Hi TwWizards
I was wondering if someone has a version of TiddlyTagMindMapPlugin
which is working with TW ver. 2.5.3 ?
I have ver. 1.5 of the plugin and it doesn't work with TW ver 2.5.3.
I haven't got an URL to the original - so I've pasted it here
(and here in a TW:
http://twspot.tiddlyspot.com/#TiddlyTagMindMapPlugin)
/***
|''Name''|TiddlyTagMindMap|
|''Description''|Bring your tiddlers to life in a radial graph which
displays all your tiddlywiki tiddlers and the relationships between
them. (A bit like The Brain)|
|''Author''|Jon Robson|
|''Contributors''|Nicolas Garcia Belmonte|
|''Version''|1.5 in progress|
|''Date''|Nov 2008|
|''Status''|@@experimental@@;|
|''License''|BSD|
|''CoreVersion''|<...>|
|''Documentation''|<...>|
|''Keywords''|data visualisation, mindmap, The Brain, Mind Manager,
FreeMind,tag relationships,graph|
!Description
Bring your TiddlyWiki to life!
!Notes
To install you will need to paste a line of text into your Theme
{{{ <div id="tagmindmap"></div>}}} in the location where you would
like to see the visualisation.
Currently we are unable to support this working in internet explorer..
we are working on it however.. sorry! :(
!Usage
{{{
The tagmindmap can be created from a macro call using
<<tiddlytagmindmap //params//>>
alternatively paste <div id='tagmindmap'></div> into your page
template.
The following macros may be useful however they can be included in the
toolbar settings of the tiddlytagmindmap macro.
<<ToggleTagMindMap id>> (create button to toggle mind map with id 'id'
on/off)
<<LoadMindMap id>> (create button to load all nodes into mind map
with id 'id')
There are a variety of configuration options in the backstage area
under tweak. They all begin with TiddlyTagMindMapPlugin:
}}}
!!Parameters
tiddlytagmindmap takes several (but all optional) parameters. Some
examples can be seen below, note the order is irrelevant of these
parameters.
!!!Nodes and Edges
!!!!Directional edges
The directed parameter allows you to add arrowheads to your edges.
usage: <<tiddlytagmindmap directed:true>>
!!!!Name Length
nodeNameLength:x where x is an integer will shorten the name of any
node with a name longer than x. If x =0, the labels will disappear so
you can rely on tooltips.
!!!!Variable node sizes (tagcloud)
notagcloud:true as a parameter will flatten the nodes to have the same
size font
!!!Dimensions
{{{<<tiddlytagmindmap height:100 width:100>>}}} will set a
tiddlytagmindmap with height and width 100.
!!!Zooming
A parameter zoom allows you to specify an integer representing the
initial inflation of the mind map. The smaller it is - the closer the
nodes will be together.
{{{<<tiddlytagmindmap zoom:1000>>}}} will give you a very inflated
TagMindMap!
!!!Breadcrumb trail
You can turn visited nodes red when they are clicked on by using the
{{{breadcrumb:true}}} parameter by default this is false.
!!!The toolbar
A parameter toolbar is a string of 1s. These signify the buttons. The
first digit sets whether the bar should appear vertically or
horizontally.
The following digits turn off or on the other available buttons.
!!!!The buttons
The digits preceding the first digit represent these buttons in this
order..
toggle, loadall
{{{<<tiddlytagmindmap toolbar:101>>}}} would give you a vertical
toolbar with a loadall button
!!!The Start State
A parameter can be used to specify how the map looks on start up.
Currently the options are empty OR all OR a custom executable
javascript function.
The first two options are simple strings eg.
{{{<<tiddlytagmindmap startState:empty>>}}} loads a blank tag mind map
however {{{<<tiddlytagmindmap startState:all>>}}} loads all nodes
excluding those in the exclude List.
the latter is more interesting. Have a look at [[Example 2]]!
!Revision History
1.5 xx/xx
*Ability to add arrow heads to show direction
*better performance
*better control panel: replacement of macros for toggle/zoom in and
out with built in toolbar to plugin
*update to new version of RGraph (JIT)
*Ability to set meta-data specific to nodes within a tiddler in
optional fields see
http://TiddlyWiki.abego-software.de/#PartTiddlerPlugin (eg.
colouring of children/parents/images in node label)
*definable meta data (prefix,suffix,label,color)
*can turn off click function
1.4 11/08 tag cloud integration/multiple tag mind maps/ability to call
from macro
1.3 22/10/08 working with ie/packaged up code
!To Do
*bug: zoomin breaks on more than 1 mind map.. no idea why but scale
resets :(
*fix ie clicking nodes
*display tiddler loads below the place it;s called from
*Ability to define your own function for relative sizing (ie. you
could weight them on some meta field)
*color property should become css property
*ability to resize using %
*distinguish between tags and tiddlers (see CreateNodeJSON - tiddlers
that don't exist in store are tagged in data field)
*rss hooks - specify where tags and node names come from in feed
*ability to specify what click function is
*ability to stop displayTiddler from loading into graph (*ability to
turn off dynamic as you go updates)
*allow resizing
*auto spread out messy nodes (ie. zoom out if nodes are too close)
*node repositioning and saving of state
*better css support
*performance issues
*deleting edges as tiddlers are deleted
*adapt to parabolic tree mode and other visualisation types
*Self-defined click functions
*Continued code cleanup
*breadcrumbs to become encoded in the node colour (rather than label
colour) define a colour of the breadcrumb trail - make it change
colour each click (increment colour, so you can get an idea of path
you took to get somewhere)
*add pins to locations in the mind map to jump back to (definable in
meta data long term, also short term in tiddler edit menu)
!Code
***/
/***
!Layer 1: TiddlyWiki Specific
Jon Robson
***/
{{{
//set descriptions
merge(config.optionsDesc,{
txtTTMM_canvasWidth: "TiddlyTagMindMapPlugin : Width of
visualisation. You will need to refresh the page to see the change."
,txtTTMM_canvasHeight: "TiddlyTagMindMapPlugin : Height of
visualisation. You will need to refresh the page to see the change."
,txtTTMM_inflation: "TiddlyTagMindMapPlugin : The visualisation can be
inflated and deflated to allow you to see the structure from above or
close up. This value allows you to set a start inflation."
,txtTTMM_maxNodeNameLength:"TiddlyTagMindMapPlugin : maximum length of
a tiddler name. Any tiddlers with names longer than this will be
abbrieviated using '...'. Set to zero to make labels disappear
altogether and rely completely on tooltips."
,chkTTMM_ignoreLoneNodes: "TiddlyTagMindMapPlugin : any lone tiddlers/
nodes (ie. tiddlers that are not tagged with any data) will be ignored
in the visualisation. This cleans up the visualisation greatly."
,txtTTMM_excludeNodeList: "TiddlyTagMindMapPlugin :Anything tagged
with this will be ignored in the visualisation. Formatted in form
['tiddlername1', 'tiddlername2']"
,chkTTMM_leaveRedBreadCrumbTrail: "TiddlyTagMindMapPlugin : When you
visit a node it will be coloured red leaving a breadcrumb trail of
where you have been."
}
);
/*MACROS*/
config.macros.TagMindMapEdge={ /* params: node1|commit node2 */
handler: function
(place,macroName,params,wikifier,paramString,tiddler) {
var id1 = params[0]; var id2 = params[1];
var ttmm =
config.macros.tiddlytagmindmap.getAssociatedTiddlyTagMindMapObject
(null,false);
if(!ttmm) return;
if(id1 == "commit") {
this.commit();
return;
}
var name1,name2,data1,data2;
if(
id1.id){
//its a json
if(
id1.name)name1 =
id1.name;
if(id1.data)data1 = id1.data;
id1 =
id1.id;
}
if(
id2.id){
//its a json
if(
id1.name)name2 =
id2.name;
if(id2.data)data2 = id2.data;
id2 =
id2.id;
}
if(!data1){ data1 = {};}
if(!data2){ data2 = {};}
if(!store.tiddlerExists(id1)){
if(!data1.color && ttmm.settings.emptyTiddlerColor)data1.color =
ttmm.settings.emptyTiddlerColor;
data1.emptyTiddler = true;
}
if(!store.tiddlerExists(id2)){
if(!data2.color && ttmm.settings.emptyTiddlerColor)data2.color =
ttmm.settings.emptyTiddlerColor;
data2.emptyTiddler = true;
}
if(ttmm){
ttmm.drawEdge(id1,id2,name1,name2,data1,data2);
}
}
,commit: function(){
var ttmm =
config.macros.tiddlytagmindmap.getAssociatedTiddlyTagMindMapObject
(null,false);
ttmm.computeThenPlot();
}
};
config.macros.LoadMindMap={
label: "loadall",
prompt: "load all tiddlers into the tag mind map",
handler: function
(place,macroName,params,wikifier,paramString,tiddler) {
var btn =createTiddlyButton
(place,this.label,this.prompt,this.onClick);
if(params[0]){
btn.wrapperID = params[0];
}
}
,onClick: function(e,id)
{
var ttmm;
if(id)
ttmm = config.macros.tiddlytagmindmap.store[id];
else
ttmm =
config.macros.tiddlytagmindmap.getAssociatedTiddlyTagMindMapObject
(this.wrapperID,true);
var list = store.getTiddlers();
for (var t=0; t<list.length; t++) {
ttmm.createNodeFromJSON(config.macros.tiddlytagmindmap.createJSON
(list[t].title,ttmm));
}
ttmm.computeThenPlot();
}
};
config.macros.ToggleTagMindMap={
label: "toggle",
prompt: "Toggle on or off the tag mind map",
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
var btn = createTiddlyButton
(place,this.label,this.prompt,this.onClick);
if(params[0])btn.wrapperID = params[0];
}
,onClick: function(e){
var id =
config.macros.tiddlytagmindmap.getAssociatedTiddlyTagMindMapObject
(this.wrapperID,true).
wrapper.id;
if(document.getElementById(id).style.display== "none"){
document.getElementById(id).style.display= "";
}
else{
document.getElementById(id).style.display= "none";
}
}
};
config.macros.tiddlytagmindmap={
store: {}, //a library of created ttmm objects
handler: function
(place,macroName,params,wikifier,paramString,tiddler) {
if(!place) { //give a default place
place = document.getElementById('tagmindmap');
if(!place) throw "no place to put ttmm!"; //unable to create
paramString =place.getAttribute("parameters");
}
var settings = this.get_ttmm_settings(paramString);
var elem = this.setup_ttmm_html(place,settings);
try{
this.store[
elem.id]= new Tagmindmap(elem,settings);
this.store['last'] =
elem.id;
this.store['cur'] =
elem.id;
if(settings.startupFunction){
settings.startupFunction(
elem.id);
}
}
catch(e){console.log("exception thrown during tiddlytagmindmap
creation:"+e);}
},
get_ttmm_setting_defaults: function(){
if(!config.options.txtTTMM_canvasWidth)
config.options.txtTTMM_canvasWidth = 800;
if(!config.options.txtTTMM_canvasHeight)
config.options.txtTTMM_canvasHeight = 200;
if(!config.options.txtTTMM_inflation)
config.options.txtTTMM_inflation = 80;
if(!config.options.txtTTMM_maxNodeNameLength)
config.options.txtTTMM_maxNodeNameLength = 25;
if(config.options.chkTTMM_ignoreLoneNodes == null)
config.options.chkTTMM_ignoreLoneNodes = false;
if(config.options.chkTTMM_leaveRedBreadCrumbTrail == null)
config.options.chkTTMM_leaveRedBreadCrumbTrail = true;
if(!config.options.txtTTMM_excludeNodeList)
config.options.txtTTMM_excludeNodeList= ['excludeLists'];
var settings = {};
/*set some defaults based on tweaked preferences if none passed as
parameters*/
settings.maxNodeNameLength =
config.options.txtTTMM_maxNodeNameLength;
settings.ignoreLoneNodes = config.options.chkTTMM_ignoreLoneNodes;
settings.tagcloud = {off:false};
settings.width = config.options.txtTTMM_canvasWidth;
settings.height =config.options.txtTTMM_canvasHeight;
settings.toolbar = "010";
settings.zoomLevel = parseInt(config.options.txtTTMM_inflation);
settings.breadcrumbs =
config.options.chkTTMM_leaveRedBreadCrumbTrail;
var clickfunction = function(node,id,e){
//var tiddlerElem = story.findContainingTiddler(resolveTarget(e));
story.displayTiddler(null,
node.id,null,null,null,null,null,id);
};
settings.clickFunction = clickfunction;
return settings;
},
get_ttmm_settings: function(paramString){
var settings = this.get_ttmm_setting_defaults();
var exList = null;
var that = this;
var startupFunction = function(id){};
settings.dynamicUpdateFunction = this.createJSON;
if(paramString){
var prms = paramString.parseParams(null, null, true);
settings.breadcrumbs = eval(getParam(prms, "breadcrumbs"));
settings.ignoreLoneNodes = eval(getParam(prms, "ignoreLoneNodes"));
settings.arrowheads = eval(getParam(prms, "directed"));
settings.tagcloud.off = eval(getParam(prms, "notagcloud"));
if(getParam(prms, "id"))
settings.id = getParam(prms, "id");
if(getParam(prms, "width")) settings.width = getParam(prms,
"width");
if(getParam(prms, "height"))settings.height = getParam(prms,
"height");
if(getParam(prms, "toolbar")) settings.toolbar= getParam(prms,
"toolbar");
if(getParam(prms,"zoom")) settings.zoomLevel = parseInt(getParam
(prms,"zoom"));
if(getParam(prms,"maxNodeNameLength"))settings.maxNodeNameLength =
getParam(prms,"maxNodeNameLength");
if(getParam(prms, "exclude")) exList = getParam(prms, "exclude");
if(getParam(prms,"displayemptytiddlers"))
settings.displayemptytiddlers = getParam(prms,"displayemptytiddlers");
if(getParam(prms,"emptyTiddlerColor")){
settings.emptyTiddlerColor =getParam(prms,"emptyTiddlerColor");
}
if(getParam(prms, "click") == "none") {
settings.clickFunction = function(node,id){return;} ;
}
else if(getParam(prms, "click") == "existing") {
settings.clickFunction = function(node,id,e){
if(!node.data.emptyTiddler){
//var tiddlerElem = story.findContainingTiddler(resolveTarget
(e));
story.displayTiddler(null,
node.id,null,null,null,null,null,id);
}
return;
} ;
}
var startState = getParam(prms, "startState");
if(startState){
if(startState == 'all')
startupFunction = function(id){
config.macros.LoadMindMap.onClick(null,id);
}
else if(startState == 'empty'){
startupFunction = function(id){
};
}
/*else if(startState == 'default'){
startupFunction = function(id){
con
var startState = store.filterTiddlers(store.getTiddlerText
("DefaultTiddlers"));
that.loadTiddlersIntoTTMM(startState,id);
}
}*/
else{//parse as a list of tiddler names
if(startState){
startupFunction = function(id){
if(startState.length == 0) return;
that.loadTiddlersIntoTTMM(startState,id);
}
}
}
settings.startupFunction = startupFunction;
}
}
if(!exList){
exList = [];
if(config.options.txtTTMM_excludeNodeList)exList =
config.options.txtTTMM_excludeNodeList;
}
/*set the excluded nodes */
var l = eval(exList);
settings.excludeNodeList = [];
for(var i=0; i < l.length; i++){
settings.excludeNodeList.push(l[i]);
settings.excludeNodeList = settings.excludeNodeList.concat
(getChildren(l[i]));
}
return settings;
}
,setup_ttmm_html: function(place,settings){
if(
place.id == 'tagmindmap')
settings.id = 'default';
if(place.style.width) settings.width = place.style.width;
if(place.style.height) settings.height = place.style.height;
/*setup tag mind map */
var newTTMM = document.createElement("div");
if(!
settings.id)
settings.id="ttmm_" +Math.random();
newTTMM.id =
settings.id;
newTTMM.style.width = settings.width +"px";
newTTMM.style.height= settings.height +"px";
newTTMM.setAttribute("class","ttmm");
/*setup toolbar */
var toolbar = document.createElement("div");
toolbar.setAttribute("class","ttmm_toolbar");
var html="", divider = " ";
if(settings.toolbar[0] == 1){//MAKE VERTICAL
toolbar.style.height = "1px";
toolbar.style.position = "relative";
var temp = parseInt(settings.width) +10;
toolbar.style.top = "0px";
toolbar.style.left = temp+"px";
divider = "\n";
}
if(settings.toolbar[1] == 1) html += "<<ToggleTagMindMap " +
newTTMM.id+">>" + divider;
if(settings.toolbar[2] == 1) html += "<<LoadMindMap " + newTTMM.id
+">>"+ divider;
place.appendChild(toolbar);
wikify(html,toolbar);
place.appendChild(newTTMM);
if(!document.getElementById(this.store['first']))this.store['first']
= newTTMM.id; //no primary ttmm exists
if(!newTTMM.style.width) newTTMM.style.width = settings.width;
if(!newTTMM.style.height) newTTMM.style.height = settings.height;
return newTTMM;
}
,loadTiddlersIntoTTMM: function(tiddlerList,visualisationID){
var viz;
if(!visualisationID)
viz = this.store[this.store['first']];
else
viz = this.store[visualisationID];
var nodesLoaded = false;
var title ="";var firstTitle="";
for(var i =0; i < tiddlerList.length; i++){
if(tiddlerList[i].title) title = tiddlerList[i].title;
else title = tiddlerList[i];
if(i==0) firstTitle = title;
nodesLoaded = viz.createNodeFromJSON(this.createJSON(title,viz)) |
nodesLoaded;
}
if(viz.rgraph){
if(nodesLoaded !=0){
viz.rgraph.compute();
viz.rgraph.plot();
}
viz.centerOnNode(title);
}
}
,getAssociatedTiddlyTagMindMapObject: function(id,getFirstCreatedTTMM)
{
if(id) return this.store[id];
else {
if(getFirstCreatedTTMM)
return this.store[this.store['first']];
else
return this.store[this.store['last']];
}
}
,_createJSONTagMindMapNodes: function(mylist,storeElement) {
var res=[];
for (var t=0; t<mylist.length; t++){
var node =mylist[t];
res.push(config.macros.tiddlytagmindmap._createJSONTagMindMapNode
(node,storeElement));
}
return res;
}
,_createJSONTagMindMapNode: function(id,storeElement){
var json = {};
json.id = id;
json.name = id;
nodeData = {};
var parents = getParents(id);
for(var i=0; i < parents.length; i++){
var nodeid = parents[i];
if(store.tiddlerExists(nodeid)){
var tiddler = store.getTiddler(nodeid);
if(tiddler.fields.childrencolor) nodeData.color =
tiddler.fields.childrencolor;
}
}
var children = getChildren(id);
for(var i=0; i < children.length; i++){
var nodeid = children[i];
if(store.tiddlerExists(nodeid)){
var tiddler = store.getTiddler(nodeid);
if(tiddler.fields.parentcolor) nodeData.color =
tiddler.fields.parentcolor;
}
}
if(store.tiddlerExists(id)){
var tiddler = store.getTiddler(id);
if(tiddler.fields.nodecolor) nodeData.color =
tiddler.fields.nodecolor;
if(tiddler.fields.nodeprefix) {
var place =createTiddlyElement(null,"div");
wikify(tiddler.fields.nodeprefix,place);
nodeData.nodeLabelPrefix = place;
}
if(tiddler.fields.nodesuffix) {
var place =createTiddlyElement(null,"div");
wikify(tiddler.fields.nodesuffix,place);
nodeData.nodeLabelSuffix = place;
}
if(tiddler.fields.nodelabel) {
var place =createTiddlyElement(null,"div");
wikify(tiddler.fields.nodelabel,place);
nodeData.label = place;
}
if(tiddler.fields.nodetooltip) {nodeData.title =
tiddler.fields.nodetooltip;}
}
if(!nodeData.color){
var empty = false;
if(!store.tiddlerExists(id) && !store.isShadowTiddler(id)){
empty = true;
}
if(store.tiddlerExists(id)){
var tiddler = store.getTiddler(id);
if(tiddler.text == null || tiddler.text == "") empty =true;
}
if(empty){
nodeData.emptyTiddler = true;
//nodeData.color = "#cccccc";
//console.log(storeElement.settings);
if(storeElement && storeElement.settings.emptyTiddlerColor)
nodeData.color= storeElement.settings.emptyTiddlerColor;
//storeElement
}
}
if(nodeData){
json.data =nodeData;
}
return json;
}
,createJSON: function(nodeid,storeElement){
if(!nodeid) return "{}";
var myjson = {};
var children = getChildren(nodeid);
var parents = getParents(nodeid);
myjson.children =
config.macros.tiddlytagmindmap._createJSONTagMindMapNodes
(children,storeElement);
myjson.parents =
config.macros.tiddlytagmindmap._createJSONTagMindMapNodes
(parents,storeElement);
myjson.node =
config.macros.tiddlytagmindmap._createJSONTagMindMapNode
(nodeid,storeElement);
return myjson;
}
};
function getParents(a){
if(store.getTiddler(a)){
return store.getTiddler(a).tags;
}
else
return [];
}
function getChildren(a){
if(store.getTaggedTiddlers(a)){
var tags = store.getTaggedTiddlers(a);
if(tags.length == 0) return [];
var a = new Array();
for (var t=0; t<tags.length; t++) {
a.push(tags[t].title);
}
return a;
}
else
return [];
}
story.beforettmm_displayTiddler = story.displayTiddler;
story.displayTiddler = function
(srcElement,tiddler,template,animate,unused,customFields,toggle,visualisationID)
{
try{
if(!document.getElementById(config.macros.tiddlytagmindmap.store
['first'])) {
config.macros.tiddlytagmindmap.handler(); //try and setup a default
one
}
}
catch(e){
};
var title = (tiddler instanceof Tiddler)? tiddler.title : tiddler;
if(config.macros.tiddlytagmindmap.store){
try{
if(!visualisationID && config.macros.tiddlytagmindmap.store
['first']) { //call came from outside tagmindmap
visualisationID =config.macros.tiddlytagmindmap.store['first'];
}
if(visualisationID){
res = config.macros.tiddlytagmindmap.loadTiddlersIntoTTMM
([title],visualisationID);
}
}
catch(e){
console.log("exception in display tiddler for "+title+" in
visualisation" + visualisationID +": " + e);
}
}
story.beforettmm_displayTiddler
(srcElement,tiddler,template,animate,unused,customFields,toggle);
};
}}}
/***
296.56
58 * 151.46
246.56
!Layer 2: DynamicInteract: Extension of RGraph
***/
{{{
Array.prototype.contains = function(item)
{
return this.indexOf(item) != -1;
};
if(!Array.indexOf) {
Array.prototype.indexOf = function(item,from)
{
if(!from)
from = 0;
for(var i=from; i<this.length; i++) {
if(this[i] === item)
return i;
}
return -1;
};
}
var Tagmindmap = function(wrapper,settings){
if(settings.clickFunction)
this.callWhenClickOnNode = settings.clickFunction;
else
this.callWhenClickOnNode = function(node,id){return};
if(settings.dynamicUpdateFunction)
this.dynamicUpdateFunction = settings.dynamicUpdateFunction;
else
this.dynamicUpdateFunction = function(node,id){return {};};
this.wrapper = wrapper;
this._setup(settings);
//this._init_html_elements(
wrapper.id);
this.controlpanel =new EasyMapController(this,wrapper);
this._init_html_elements();
var x = this.controlpanel;
initialT = {translate: {x:0,y:0}, scale:
{x:this.settings.zoomLevel,y:this.settings.zoomLevel}};
x.setTransformation(initialT);
x.addControl("zoom");
x.addControl("pan");
x.addControl("mousepanning");
x.addControl("mousewheelzooming");
this.children = {};
this.parents = {};
};
Tagmindmap.prototype = {
transform: function(t){
var compute = false;
if(this.settings.zoomLevel != t.scale.x) {
if(t.scale.x > 0){
this.settings.zoomLevel = parseFloat(t.scale.x);
}
compute = true;
}
if(this.rgraph){
var c= {x:t.translate.x*t.scale.x, y:t.translate.y*t.scale.y};
this.rgraph.offsetCenter(c.x,c.y);
if(compute) this.rgraph.compute();
this.rgraph.plot();
}
},
_setup: function(settings){
this.settings = {'arrowheads':false,'maxNodeNameLength':
99999,'breadcrumbs':
true,'lineColor':'#ccddee','nodeColor':'#ccddee','zoomLevel':120,
'ignoreLoneNodes':false,'excludeNodeList': ['excludeLists']}; //put
all default settings here
this.settings.tagcloud = {'smallest': 1, 'largest': 1.6, 'upper':0,
'off': false}; //upper is the maximum sized node
this.graph_showCirclesFlag = false; //shows circles in the mind map
this.maxNodeNameLength = 0;
this.displacement = {'x':0, 'y':0};
this.maxChildrenOnSingleNode = 0;
this.thehiddenbridge = "RGRAPHTREEBRIDGE"; //a hidden node which
bridges all dislocated nodes.
this.settings.breadcrumb_startcolor = "#b7e715"; //rgb(0,0,0)
/*above defaults below read in */
for(var i in settings){
this.settings[i] = settings[i];
}
this.settings.arrowheads = settings.arrowheads;
this.settings.breadcrumbs = settings.breadcrumbs;
this.settings.tagcloud.off = settings.tagcloud.off;
this.settings.excludeNodeList = settings.excludeNodeList;
this.settings.ignoreLoneNodes = settings.ignoreLoneNodes;
this.maxNodeNameLength = settings.maxNodeNameLength;
this.settings.zoomLevel = settings.zoomLevel;
var ttmm = this;
},
_init_html_elements: function(){
var wrapperID =
this.wrapper.id;
if(!document.getElementById(wrapperID)){ throw (wrapperID + " html
element doesn't exist");}
var canvasID = wrapperID + "_canvas"; //the canvas object ID
this.labelContainer = wrapperID + "_label_container";
this.nodeLabelPrefix = canvasID +"_";
/*setup the divs */
var wrapper = this.wrapper;
wrapper.style.position = "relative";
if(!wrapper.style.height){wrapper.style.height = "200px";}
if(!wrapper.style.width){wrapper.style.width = "200px";}
var labelContainer = document.createElement("div");
labelContainer.id=this.labelContainer;
labelContainer.style.position= 'relative';
var canvas = document.createElement("canvas");
canvas.id = canvasID;
canvas.width = parseInt(wrapper.style.width);
canvas.height =parseInt(wrapper.style.height);
wrapper.appendChild(labelContainer);
wrapper.appendChild(canvas);
this.canvas = canvas;
if(config.browser.isIE && G_vmlCanvasManager)
{G_vmlCanvasManager.init_(document);} //ie hack - needs changing to
work outside tw
},
createNodeFromJSON: function(json){
if(json == {}) return;
var temp = false;
var res = false;
var node1= json['node'];
if(json['parents']){
for(var i=0; i < json['parents'].length; i++){
var parent = json['parents'][i];
temp = this.drawEdge(parent['id'],node1['id'],parent['name'],node1
['name'],parent['data'],node1['data']);
res = temp | res;
}
}
if(json['children']){
for(var i=0; i < json['children'].length; i++){
var child = json['children'][i];
temp = this.drawEdge(node1['id'],child['id'],node1['name'],child
['name'],node1['data'],child['data']);
res = temp | res;
}
}
if(json['children'] && json['parents']){
if(!this.settings.ignoreLoneNodes && json['children'].length ==0 &&
json['parents'].length == 0)
temp = this.drawEdge(this.thehiddenbridge, node1['id'],null,node1
['name'],null,node1['data']);
res = temp | res;
}
return res;
},
centerOnNode:function(id){
//var cur =this.getCurrentNodeID();
//if(cur == id) return;
this.rgraph.onClick(id);
},
getCurrentNodeID: function(){
if(!this.rgraph.graph.root) return false;
if(
this.rgraph.graph.root.id == this.thehiddenbridge) return false;
else return
this.rgraph.graph.root.id;
},
setNodeName: function(nodeid,newName){
var node = this.controller.getNode(nodeid);
if(
node.name != newName){
node.name = newName;
if(this.thehiddenbridge != nodeid && this.graph_index)
this.graph_index[newName] = nodeid;
}
},
mergeNodeData: function(id,data){
var node = this.controller.getNode(id);
if(!node) return;
for (var key in data){
if(typeof node.data[key] == 'array')
node.data[key] = node.data[key].concat(data[key]);
else
node.data[key] = data[key];
}
if(node.data.weight > this.settings.tagcloud.upper) {
this.settings.tagcloud.upper = node.data.weight;
}
},
setNodeData: function(id,data,newvalue){
var node = this.controller.getNode(id);
if(!node) return;
if(!newvalue){
node.data = data;
}
else{
node.data[data] = newvalue;
}
if(node.data.weight > this.settings.tagcloud.upper) {
this.settings.tagcloud.upper = node.data.weight;
}
},
_nodeInExcludeList: function(id){
return this.settings.excludeNodeList.contains(id);
},
drawEdge: function(id_a,id_b,name_a,name_b,data_a,data_b){
if(this._nodeInExcludeList(id_a) || this._nodeInExcludeList(id_b))
return false;
plotNeeded=false;
if(id_a != "" && id_b != ""){
plotNeeded = this._make_connection(id_a,id_b);
if(name_a){this.setNodeName(id_a,name_a);}
if(name_b){this.setNodeName(id_b,name_b);}
if(data_a) {this.mergeNodeData(id_a,data_a); }
if(data_b) {this.mergeNodeData(id_b,data_b);}
}
return plotNeeded;
},
_make_connection: function(a,b){
var drawn = this._setupMapIfNeeded(a);
var node1, node2;
node1 = this.controller.getNode(a);
node2 = this.controller.getNode(b);
if(node1 && node2){
if(node1.adjacentTo(node2)) {return false;}
}
else if(!node1 && !node2) {//neither in graph yet
drawn = this._make_connection(this.thehiddenbridge,a); //if
neither node is currently in tree, then we need to create a "bridge"
to connect the trees
}
if(!node1) {node1= new Graph.Node(a,a,{});drawn= true; }//create
this node
if(!node2) {node2= new Graph.Node(b,b,{});drawn= true; }//create
that node
if(node1){
if(!node1.adjacentTo(node2)){
this.controller.addAdjacence(node1,node2);
node1 = this.controller.getNode(a);
node2 = this.controller.getNode(b);
if(!this.children[a]) this.children[a] = [];
if(!this.parents[b]) this.parents[b] = [];
this.children[a].push(b);
this.parents[b].push(a);
return true;
}
}
},
deleteNode: function(id){
var node = this.rgraph.controller.getNode(id);
//console.log("start",node,"end");
var parents = node.data.parents;
var children = node.data.children;
//console.log(id,parents,children);
if(children){
//sort out children
for(var i=0; i < children.length; i++){
var childNode = this.rgraph.controller.getNode(children[i]);
var oldparents = childNode.data.parents;
var newparents = [];
for(var j=0; j < oldparents.length; j++){
if(oldparents[j] != id)newparents.push(oldparents[j]);
}
this.setNodeData(children[i],"parents",newparents);
if(newparents.length == 0) { //connect it up to the bridge
this.drawEdge(this.thehiddenbridge,children[i]);
}
}
}
//sort out parents
if(parents){
for(var i=0; i < parents.length; i++){
if(parents[i] != this.thehiddenbridge){
var parentNode = this.rgraph.controller.getNode(parents[i]);
var oldchildren = parentNode.data.children;
var newchildren = [];
for(var j=0; j < oldchildren.length; j++){
if(oldchildren[j] != id)newchildren.push(oldchildren[j]);
}
this.setNodeData(parents[i],"children",newchildren);
}
}
}
this.rgraph.controller.removeNode(id);
},
computeThenPlot: function(){
try{
this.rgraph.compute();
this.rgraph.plot();
}
catch(e){
console.log(e+"in computeThenPlot");
}
},
_trimNodeName: function(node_name){
if(this.maxNodeNameLength ==0) return "<span> </
span>";
if(this.maxNodeNameLength){
var nlength = this.maxNodeNameLength;
if(node_name.length > nlength)
return node_name.substr(0,nlength/2) + "..." + node_name.substr
(node_name.length-nlength/2,node_name.length);
else
return node_name;
}
return node_name;
},
_getController: function(){
var ttmm = this;
var effectHash = {};
var controller = {
removeNode: function(id){
var el = document.getElementById(this.getNodeLabelPrefix()+id);
el.parentNode.removeChild(el);
var graph = ttmm.rgraph.graph;
if(graph) graph.removeNode(id);
},
getNode: function(id){
var n = GraphUtil.getNode(ttmm.rgraph.graph,id);
return n;
},
addAdjacence: function(node1,node2){
ttmm.rgraph.graph.addAdjacence(node1,node2);
},
/*some custom defined controller operations (search in RGraph
source)*/
getZoomLevel: function(){
return parseFloat(ttmm.settings.zoomLevel);
},
setOffset: function(d){ttmm.displacement = d;},
getOffset: function(){return ttmm.displacement;},
getNodeLabelContainer: function(){
return ttmm.labelContainer;
},
getNodeLabelPrefix: function(){return ttmm.nodeLabelPrefix;},
onBeforeCompute: function(node) {
ttmm.createNodeFromJSON(ttmm.dynamicUpdateFunction(
node.id));
if(ttmm.settings.breadcrumbs) {
ttmm.setNodeData
(
node.id,"color",ttmm.settings.breadcrumb_startcolor);
}
},
getName: function(node1, node2) {
for(var i=0; i<node1.data.length; i++) {
var dataset = node1.data[i];
if(dataset.key ==
node2.name) return dataset.value;
}
for(var i=0; i<node2.data.length; i++) {
var dataset = node2.data[i];
if(dataset.key ==
node1.name) return dataset.value;
}
},
onCreateLabel: function(domElement, node) {
}
,attachClickFunction: function(domElement,node){
if(
node.id == this.thehiddenbridge) return;
var clickfunction = function(event){
if(ttmm.rgraph.root ==
node.id){ //special case for when node is
already centered
ttmm.callWhenClickOnNode(node,
ttmm.wrapper.id,event);
return;
}
else{ //need to center first
var t = ttmm.controlpanel.transformation;
t.translate = {x:0,y:0};
ttmm.controlpanel.setTransformation(t);
ttmm.rgraph.onClick(
node.id);
var todo = function(){
ttmm.callWhenClickOnNode(node,
ttmm.wrapper.id,event);
};
ttmm._afterComputeFunction = todo;
}
return false;
};
if(domElement.addEvent){ //for ie
domElement.addEvent('click',clickfunction);
}
else {
domElement.onclick = clickfunction;
}
}
,getMaxChildren: function(){
var max =0,num;
for(var i in ttmm.children){
if(ttmm.children[i])
num = ttmm.children[i].length;
else
num =0;
if(num > max) max = num;
}
return max;
},
calculateNodeWeight: function(node){
var weight=0, u=0;
if(node.data.weight) { //user has defined some sort of weight
weight = parseFloat(node.data.weight);
u =parseFloat(ttmm.settings.tagcloud.upper);
}
else{ //just take number of children
if(ttmm.children[
node.id]){
weight = ttmm.children[
node.id].length;
}
u = this.getMaxChildren();
}
var s,l;
if(ttmm.settings.tagcloud.smallest){
s = parseFloat(ttmm.settings.tagcloud.smallest);
}
else{
s = 0.5;
}
if(ttmm.settings.tagcloud.largest) {
l =parseFloat(ttmm.settings.tagcloud.largest);
}
else{
l = 2;
}
var fontsize = s + ((l - s) * parseFloat(weight / u));
//console.log(s,l,weight,u,fontsize);
return fontsize;
},
onPlaceLabel: function(domElement, node) {
domElement.innerHTML = ""; //quick and dirty flush
if(
node.id != ttmm.thehiddenbridge){
if(node.data.color) domElement.style.color = node.data.color;
if(node.data.title){
domElement.title = node.data.title;
}
else{
domElement.title =
node.name;
}
var prefix, nodeLabel,suffix;
if(node.data.nodeLabelPrefix) prefix =node.data.nodeLabelPrefix;
if(prefix){
prefix.setAttribute("class","nodeLabelPrefix");
domElement.appendChild(prefix);
}
if(!node.data.label){
nodeLabel = document.createElement("span");
var labelText = ttmm._trimNodeName(
node.name);
nodeLabel.appendChild(document.createTextNode(labelText));
}
else{
nodeLabel = node.data.label;
}
if(!ttmm.settings.tagcloud.off){
var fontsize = this.calculateNodeWeight(node);
nodeLabel.style.fontSize = fontsize + "em";
}
nodeLabel.setAttribute("class","nodeLabel");
this.attachClickFunction(nodeLabel,node);
domElement.appendChild(nodeLabel);
if(node.data.nodeLabelSuffix) suffix =node.data.nodeLabelSuffix;
if(suffix){
suffix.setAttribute
("class","nodeLabelSuffix");
domElement.appendChild(suffix);
}
}
else domElement.style.display = "none";
var left = parseInt(domElement.style.left);
domElement.style.width = '';
domElement.style.height = '';
var w = domElement.offsetWidth;
domElement.style.left = (left - w /2) + 'px';
},
onAfterCompute: function() {
if(ttmm._afterComputeFunction){
ttmm._afterComputeFunction();
ttmm._afterComputeFunction = false;
}
},
onBeforePlotLine: function(adj){
lineW = ttmm.canvas.getContext().lineWidth;
nodeid =
adj.nodeFrom.id;
nodeid2 =
adj.nodeTo.id;
if(nodeid == ttmm.thehiddenbridge || nodeid2 ==
ttmm.thehiddenbridge){
ttmm.canvas.getContext().lineWidth = "0";
}
else ttmm.canvas.getContext().lineWidth = "1";
},
onAfterPlotLine: function(adj){
var l =this.getNodeLabelContainer();
//document.getElementById(l).innerHTML = "";
var context = ttmm.canvas.getContext();
var canvas = ttmm.canvas;
var node = adj.nodeFrom, child = adj.nodeTo;
var pos = node.pos.toComplex();
var posChild = child.pos.toComplex();
var d = this.getOffset();//jon
//draw arrowhead.. (angle needs to be calculated)
if(ttmm.settings.arrowheads){
if(
node.id == ttmm.thehiddenbridge)return;
//console.log("arrowhead from",
node.id, "to",
child.id)
canvas.path('stroke', function(context) {
var r = 20;
var ctx = context;
ctx.save();
//ctx.beginPath();
ctx.translate(posChild.x +d.x,posChild.y+d.y);
var o = parseFloat(posChild.y-pos.y);
var a = parseFloat(posChild.x -pos.x);
if(a !=0){
var rad = Math.atan2(o,a);
ctx.rotate(rad);
ctx.moveTo(2,0);
ctx.lineTo(-r,-4);
ctx.lineTo(-r,4);
ctx.lineTo(2,0);
ctx.fill();
}
ctx.restore();
});
}
ttmm.canvas.getContext().lineWidth = "1";
}
};
return controller;
},
_setupMapIfNeeded: function(lastOpenNode){
if(!this.canvas){
this._init_html_elements();
}
var ctx = this.canvas.getContext;
if(!ctx) {console.log("no context available! Please install
ExplorerCanvas");}
if(this.graphloaded) return false;
this.graphloaded = true;
//this._firstnode =lastOpenNode;
var json = {"id":this.thehiddenbridge,"children":
[{"id":lastOpenNode,"name":lastOpenNode, "data":{"parents":
[this.thehiddenbridge], "children":[]}, "children":[]}], 'data':
{"parents":[], "children":[lastOpenNode]}};
json.data = {};
json.data.nodraw=true;
this.canvas= new Canvas(
this.canvas.id, this.settings.nodeColor,
this.settings.lineColor);
controller = this._getController();
this.rgraph= new RGraph(this.canvas,
controller,this.labelContainer);
Config['drawConcentricCircles'] = this.graph_showCirclesFlag;
this.rgraph.loadTreeFromJSON(json);
this.controller = controller;
this.rgraph.compute();
this.centerOnNode(lastOpenNode);
//this.rgraph.graph.root = this.controller.getNode(lastOpenNode);
//if(!this.rgraph.graph.root) this.rgraph.graph.root
=this.controller.getNode(this._firstNode);
return true;
}
};
}}}
/*
* File: RGraph.js
*
* Author: Nicolas Garcia Belmonte
*
* Copyright: Copyright 2008 by Nicolas Garcia Belmonte.
*
* Homepage: <
http://thejit.org>
*
* Version: 1.0.7a
*
* License: BSD License
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
are met:
* * Redistributions of source code must retain the above
copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
copyright
* notice, this list of conditions and the following disclaimer
in the
* documentation and/or other materials provided with the
distribution.
* * Neither the name of the organization nor the
* names of its contributors may be used to endorse or promote
products
* derived from this software without specific prior written
permission.
*
* THIS SOFTWARE IS PROVIDED BY Nicolas Garcia Belmonte ``AS IS'' AND
ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE
* DISCLAIMED. IN NO EVENT SHALL Nicolas Garcia Belmonte BE LIABLE FOR
ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
Object: $_
Provides some common utility functions.
*/
var $_ = {
fn: function() { return function() {}; },
merge: function(){
var mix = {};
for (var i = 0, l = arguments.length; i < l; i++){
var object = arguments[i];
if (typeof object != 'object') continue;
for (var key in object){
var op = object[key], mp = mix[key];
mix[key] = (mp && typeof op == 'object' && typeof mp ==
'object') ? this.merge(mp, op) : this.unlink(op);
}
}
return mix;
},
unlink: function (object){
var unlinked = null;
if(this.isArray(object)) {
unlinked = [];
for (var i = 0, l = object.length; i < l; i++) unlinked[i] =
this.unlink(object[i]);
} else if(this.isObject(object)) {
unlinked = {};
for (var p in object) unlinked[p] = this.unlink(object[p]);
} else return object;
return unlinked;
},
isArray: function(obj) {
return obj.constructor.toString().match(/array/i);
},
isString: function(obj) {
return obj.constructor.toString().match(/string/i);
},
isObject: function(obj) {
return obj.constructor.toString().match(/object/i);
}
} ;
/*
Class: Canvas
A multi-purpose Canvas object decorator.
*/
/*
Constructor: Canvas
Canvas initializer.
Parameters:
canvasId - The canvas tag id.
fillStyle - (optional) fill color style. Default's to black
strokeStyle - (optional) stroke color style. Default's to black
Returns:
A new Canvas instance.
*/
var Canvas= function (canvasId, fillStyle, strokeStyle) {
//browser supports canvas element
this.canvasId= canvasId;
this.fillStyle = fillStyle;
this.strokeStyle = strokeStyle;
//canvas element exists
if((this.canvas= document.getElementById(this.canvasId))
&& this.canvas.getContext) {
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = fillStyle || 'black';
this.ctx.strokeStyle = strokeStyle || 'white';
this.setPosition();
this.translateToCenter();
} else {
throw "Canvas object could not initialize.";
}
};
Canvas.prototype= {
/*
Method: getContext
Returns:
Canvas context handler.
*/
getContext: function () {
return this.ctx;
},
/*
Method: setPosition
Calculates canvas absolute position on HTML document.
*/
setPosition: function() {
var obj= this.canvas;
var curleft = curtop = 0;
if (obj.offsetParent) {
curleft = obj.offsetLeft
curtop = obj.offsetTop
while (obj = obj.offsetParent) {
curleft += obj.offsetLeft
curtop += obj.offsetTop
}
}
this.position= { x: curleft, y: curtop };
},
/*
Method: getPosition
Returns:
Canvas absolute position to the HTML document.
*/
getPosition: function() {
return this.position;
},
/*
Method: clear
Clears the canvas object.
*/
clear: function () {
this.ctx.clearRect(-this.getSize().x / 2, -this.getSize().x / 2,
this.getSize().x, this.getSize().x);
},
/*
Method: drawConcentricCircles
Draws concentric circles. Receives an integer specifying the
number of concentric circles.
*/
drawConcentricCircles: function (elem) {
var config = Config;
var times = elem || 6;
var c = this.ctx;
c.strokeStyle = config.concentricCirclesColor;
var pi2 = Math.PI*2;
for(var i=1; i<=times; i++) {
c.beginPath();
c.arc(0, 0, (i*config.levelDistance), 0, pi2, true);
c.stroke();
c.closePath();
}
c.strokeStyle = this.strokeStyle;
},
/*
Method: translateToCenter
Translates canvas coordinates system to the center of the canvas
object.
*/
translateToCenter: function() {
this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2);
},
/*
Method: getSize
Returns:
An object that contains the canvas width and height.
i.e. { x: canvasWidth, y: canvasHeight }
*/
getSize: function () {
var width = this.canvas.width;
var height = this.canvas.height;
return { x: width, y: height };
},
/*
Method: path
Performs a _beginPath_ executes _action_ doing then a _type_
('fill' or 'stroke') and closing the path with closePath.
*/
path: function(type, action) {
this.ctx.beginPath();
action(this.ctx);
this.ctx[type]();
this.ctx.closePath();
}
};
/*
Class: Complex
A multi-purpose Complex Class with common methods.
*/
/*
Constructor: Complex
Complex constructor.
Parameters:
re - A real number.
im - An real number representing the imaginary part.
Returns:
A new Complex instance.
*/
var Complex= function() {
if (arguments.length > 1) {
this.x= arguments[0];
this.y= arguments[1];
} else {
this.x= null;
this.y= null;
}
};
Complex.prototype= {
/*
Method: clone
Returns a copy of the current object.
Returns:
A copy of the real object.
*/
clone: function() {
return new Complex(this.x, this.y);
},
/*
Method: toPolar
Transforms cartesian to polar coordinates.
Returns:
A new <Polar> instance.
*/
toPolar: function() {
var rho = this.norm();
var atan = Math.atan2(this.y, this.x);
if(atan < 0) atan += Math.PI * 2;
return new Polar(atan, rho);
},
/*
Method: norm
Calculates the complex norm.
Returns:
A real number representing the complex norm.
*/
norm: function () {
return Math.sqrt(this.squaredNorm());
},
/*
Method: squaredNorm
Calculates the complex squared norm.
Returns:
A real number representing the complex squared norm.
*/
squaredNorm: function () {
return this.x*this.x + this.y*this.y;
},
/*
Method: add
Returns the result of adding two complex numbers.
Does not alter the original object.
Parameters:
pos - A Complex initialized instance.
Returns:
The result of adding two complex numbers.
*/
add: function(pos) {
return new Complex(this.x + pos.x, this.y + pos.y);
},
/*
Method: prod
Returns the result of multiplying two complex numbers.
Does not alter the original object.
Parameters:
pos - A Complex initialized instance.
Returns:
The result of multiplying two complex numbers.
*/
prod: function(pos) {
return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x +
this.x*pos.y);
},
/*
Method: conjugate
Returns the conjugate por this complex.
Returns:
The conjugate por this complex.
*/
conjugate: function() {
return new Complex(this.x, -this.y);
},
/*
Method: scale
Returns the result of scaling a Complex instance.
Does not alter the original object.
Parameters:
factor - A scale factor.
Returns:
The result of scaling this complex to a factor.
*/
scale: function(factor) {
return new Complex(this.x * factor, this.y * factor);
},
/*
Method: equals
Comparison method.
*/
equals: function(c) {
return this.x == c.x && this.y == c.y;
},
/*
Method: $add
Returns the result of adding two complex numbers.
Alters the original object.
Parameters:
pos - A Complex initialized instance.
Returns:
The result of adding two complex numbers.
*/
$add: function(pos) {
this.x += pos.x; this.y += pos.y;
return this;
},
/*
Method: $prod
Returns the result of multiplying two complex numbers.
Alters the original object.
Parameters:
pos - A Complex initialized instance.
Returns:
The result of multiplying two complex numbers.
*/
$prod:function(pos) {
var x = this.x, y = this.y
this.x = x*pos.x - y*pos.y;
this.y = y*pos.x + x*pos.y;
return this;
},
/*
Method: $conjugate
Returns the conjugate for this complex.
Alters the original object.
Returns:
The conjugate for this complex.
*/
$conjugate: function() {
this.y = -this.y;
return this;
},
/*
Method: $scale
Returns the result of scaling a Complex instance.
Alters the original object.
Parameters:
factor - A scale factor.
Returns:
The result of scaling this complex to a factor.
*/
$scale: function(factor) {
this.x *= factor; this.y *= factor;
return this;
},
/*
Method: $div
Returns the division of two complex numbers.
Alters the original object.
Parameters:
pos - A Complex number.
Returns:
The result of scaling this complex to a factor.
*/
$div: function(pos) {
var x = this.x, y = this.y;
var sq = pos.squaredNorm();
this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
return this.$scale(1 / sq);
}
};
Complex.KER = new Complex(0, 0);
/*
Class: Polar
A multi purpose polar representation.
*/
/*
Constructor: Polar
Polar constructor.
Parameters:
theta - An angle.
rho - The norm.
Returns:
A new Polar instance.
*/
var Polar = function(theta, rho) {
this.theta = theta;
this.rho = rho;
};
Polar.prototype = {
/*
Method: clone
Returns a copy of the current object.
Returns:
A copy of the real object.
*/
clone: function() {
return new Polar(this.theta, this.rho);
},
/*
Method: toComplex
Translates from polar to cartesian coordinates and returns a new
<Complex> instance.
Returns:
A new Complex instance.
*/
toComplex: function() {
return new Complex(Math.cos(this.theta), Math.sin(this.theta)).$scale
(this.rho);
},
/*
Method: add
Adds two <Polar> instances.
Returns:
A new Polar instance.
*/
add: function(polar) {
return new Polar(this.theta + polar.theta, this.rho + polar.rho);
},
/*
Method: scale
Scales a polar norm.
Returns:
A new Polar instance.
*/
scale: function(number) {
return new Polar(this.theta, this.rho * number);
},
/*
Method: equals
Comparison method.
*/
equals: function(c) {
return this.theta == c.theta && this.rho == c.rho;
},
/*
Method: $add
Adds two <Polar> instances affecting the current object.
Returns:
The changed object.
*/
$add: function(polar) {
this.theta = this.theta + polar.theta; this.rho += polar.rho;
return this;
},
/*
Method: $madd
Adds two <Polar> instances affecting the current object. The
resulting theta angle is modulo 2pi.
Returns:
The changed object.
*/
$madd: function(polar) {
this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho +=
polar.rho;
return this;
},
/*
Method: $scale
Scales a polar instance affecting the object.
Returns:
The changed object.
*/
$scale: function(number) {
this.rho *= number;
return this;
},
/*
Method: interpolate
Calculates a polar interpolation between two points at a given
delta moment.
Returns:
A new Polar instance representing an interpolation between
_this_ and _elem_
*/
interpolate: function(elem, delta) {
var pi2 = Math.PI * 2;
var ch = function(t) {
return (t < 0)? (t % pi2) + pi2 : t % pi2;
};
var tt = ch(this.theta) , et = ch(elem.theta);
var sum;
if(Math.abs(tt - et) > Math.PI) {
if(tt - et > 0) {
sum =ch((et + ((tt - pi2) - et)* delta)) ;
} else {
sum =ch((et - pi2 + (tt - (et - pi2))* delta));
}
} else {
sum =ch((et + (tt - et)* delta)) ;
}
var t = (sum);
var r = (this.rho - elem.rho) * delta + elem.rho;
return new Polar(t, r);
}
};
Polar.KER = new Polar(0, 0);
/*
Object: Config
<RGraph> global configuration object. Contains important properties
to enable customization and proper behavior for the <RGraph>.
*/
var Config= {
//Property: labelContainer
//Id for label container. The label container is a div dom element
that must be explicitly added to your page in order to enable the
RGraph.
labelContainer: 'label_container',
//Property: drawConcentricCircles
//show/hide concentricCircles
drawConcentricCircles: 4,
//Property: concentricCirclesColor
//The color of the concentric circles
concentricCirclesColor: '#444',
//Property: levelDistance
//The actual distance between levels
levelDistance: 100,
//Property: nodeRadius
//The radius of the nodes displayed
nodeRadius: 4,
//Property: allowVariableNodeDiameters
//Set this to true if you want your node diameters to be
proportional to you first dataset object value property (i.e _data
[0].value_).
//This will allow you to represent weighted tree/graph nodes.
allowVariableNodeDiameters: false,
//Property: nodeRangeDiameters
//Diameters range. For variable node weights.
nodeRangeDiameters: {
min: 10,
max: 35
},
//Property: nodeRangeValues
// The interval of the values of the first object of your dataSet.
// A superset of the values can also be specified.
nodeRangeValues: {
min: 1,
max: 35
},
//Property: fps
//animation frames per second
fps:40,
//Property: animationTime
animationTime: 2500,
//Property: interpolation
interpolation: 'linear'
};
/*
Object: GraphUtil
A multi purpose object to do graph traversal and processing.
*/
var GraphUtil = {
/*
Method: filter
For internal use only. Provides a filtering function based on
flags.
*/
filter: function(param) {
if(!param || !$_.isString(param)) return function() { return
true; };
var props = param.split(" ");
return function(elem) {
for(var i=0; i<props.length; i++) if(elem[props[i]]) return false;
return true;
};
},
/*
Method: getNode
Returns a graph's node with a specified _id_.
*/
getNode: function(graph, id) {
return graph.getNode(id);
},
/*
Method: eachNode
Iterates over graph nodes performing an action.
*/
eachNode: function(graph, action, flags) {
var filter = this.filter(flags);
for(var i in graph.nodes) if(filter(graph.nodes[i])) action
(graph.nodes[i]);
},
/*
Method: eachAdjacency
Iterates over a _node_ adjacencies applying the _action_ function.
*/
eachAdjacency: function(node, action, flags) {
var adj = node.adjacencies, filter = this.filter(flags);
for(var id in adj) if(filter(adj[id])) action(adj[id], id);
},
/*
Method: computeLevels
Performs a BFS traversal setting correct level for nodes.
*/
computeLevels: function(graph, id, flags) {
var filter = this.filter(flags);
this.eachNode(graph, function(elem) {
elem._flag = false;
elem._depth = -1;
}, flags);
var root = graph.getNode(id);
root._depth = 0;
var queue = [root];
while(queue.length != 0) {
var node = queue.pop();
node._flag = true;
this.eachAdjacency(node, function(adj) {
var n = adj.nodeTo;
if(n._flag == false && filter(n)) {
if(n._depth < 0) n._depth = node._depth + 1;
queue.unshift(n);
}
}, flags);
}
},
/*
Method: eachBFS
Performs a BFS traversal of a graph beginning by the node of id
_id_ and performing _action_ on each node.
This traversal ignores nodes or edges having the property _ignore_
setted to _true_.
*/
eachBFS: function(graph, id, action, flags) {
var filter = this.filter(flags);
this.clean(graph);
var queue = [graph.getNode(id)];
while(queue.length != 0) {
var node = queue.pop();
node._flag = true;
action(node, node._depth);
this.eachAdjacency(node, function(adj) {
var n = adj.nodeTo;
if(n._flag == false && filter(n)) {
n._flag = true;
queue.unshift(n);
}
}, flags);
}
},
/*
Method: eachSubnode
After a BFS traversal the _depth_ property of each node has been
modified. Now the graph can be traversed as a tree. This method
iterates for each subnode that has depth larger than the specified
node.
*/
eachSubnode: function(graph, node, action, flags) {
var d = node._depth, filter = this.filter(flags);
this.eachAdjacency(node, function(adj) {
var n = adj.nodeTo;
if(n._depth > d && filter(n)) action(n);
}, flags);
},
/*
Method: getSubnodes
Collects all subnodes for a specified node. The _level_ parameter
filters nodes having relative depth of _level_ from the root node.
*/
getSubnodes: function(graph, id, level, flags) {
var ans = new Array(), that = this, node = graph.getNode(id);
(function(graph, node) {
var fn = arguments.callee;
if(!level || level <= node._depth) ans.push(node);
that.eachSubnode(graph, node, function(elem) {
fn(graph, elem);
}, flags);
})(graph, node);
return ans;
},
/*
Method: getParents
Returns all nodes having a depth that is less than the node's
depth property.
*/
getParents: function(graph, node) {
var adj = node.adjacencies;
var ans = new Array();
this.eachAdjacency(node, function(adj) {
var n = adj.nodeTo;
if(n._depth < node._depth) ans.push(n);
});
return ans;
},
/*
Method: clean
Cleans flags from nodes (by setting the _flag_ property to false).
*/
clean: function(graph) { this.eachNode(graph, function(elem)
{ elem._flag = false; }); }
};
/*
Object: GraphOp
An object holding unary and binary graph operations such as
removingNodes, removingEdges, adding two graphs and morphing.
*/
var GraphOp = {
options: {
type: 'nothing',
duration: 2000,
fps:30
},
/*
Method: removeNode
Removes one or more nodes from the visualization. It can also
perform several animations like fading sequentially, fading
concurrently, iterating or replotting.
Parameters:
viz - The visualization object (an RGraph instance in this
case).
node - The node's id. Can also be an array having many ids.
opt - Animation options. It's an object with two properties:
_type_, which can be _nothing_, _replot_, _fade:seq_, _fade:con_ or
_iter_. The other property is the _duration_ in milliseconds.
*/
removeNode: function(viz, node, opt) {
var options = $_.merge(viz.controller, this.options, opt);
var n = $_.isString(node)? [node] : node;
switch(options.type) {
case 'nothing':
for(var i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
break;
case 'replot':
this.removeNode(viz, n, { type: 'nothing' });
GraphPlot.clearLabels(viz);
viz.refresh();
break;
case 'fade:seq': case 'fade':
var GPlot = GraphPlot, that = this;
//set alpha to 0 for nodes to remove.
for(var i=0; i<n.length; i++) {
var nodeObj = viz.graph.getNode(n[i]);
nodeObj.endAlpha = 0;
}
GPlot.animate(viz, $_.merge(options, {
modes: ['fade:nodes'],
onComplete: function() {
that.removeNode(viz, n, { type: 'nothing' });
GPlot.clearLabels(viz);
viz.compute('endPos');
GPlot.animate(viz, $_.merge(options, {
modes: ['linear']
}));
}
}));
break;
case 'fade:con':
var GPlot = GraphPlot, that = this;
//set alpha to 0 for nodes to remove. Tag them for being ignored
on computing positions.
for(var i=0; i<n.length; i++) {
var nodeObj = viz.graph.getNode(n[i]);
nodeObj.endAlpha = 0;
nodeObj.ignore = true;
}
viz.compute('endPos');
GPlot.animate(viz, $_.merge(options, {
modes: ['fade:nodes', 'linear'],
onComplete: function() {
that.removeNode(viz, n, { type: 'nothing' });
}
}));
break;
case 'iter':
var that = this, GPlot = GraphPlot;
GPlot.sequence(viz, {
condition: function() { return n.length != 0; },
step: function() { that.removeNode(viz, n.shift(), { type:
'nothing' }); GPlot.clearLabels(viz); },
onComplete: function() { options.onComplete(); },
duration: Math.ceil(options.duration / n.length)
});
break;
default: this.doError();
}
},
/*
Method: removeEdge
Removes one or more edges from the visualization. It can also
perform several animations like fading sequentially, fading
concurrently, iterating or replotting.
Parameters:
viz - The visualization object (an RGraph instance in this
case).
vertex - An array having two strings which are the ids of the
nodes connected by this edge: ['id1', 'id2']. Can also be a two
dimensional array holding many edges: [['id1', 'id2'], ['id3',
'id4'], ...].
opt - Animation options. It's an object with two properties:
_type_, which can be _nothing_, _replot_, _fade:seq_, _fade:con_ or
_iter_. The other property is the _duration_ in milliseconds.
*/
removeEdge: function(viz, vertex, opt) {
var options = $_.merge(viz.controller, this.options, opt);
var v = $_.isString(vertex[0])? [vertex] : vertex;
switch(options.type) {
case 'nothing':
for(var i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v
[i][1]);
break;
case 'replot':
this.removeEdge(viz, v, { type: 'nothing' });
viz.refresh();
break;
case 'fade:seq': case 'fade':
var GPlot = GraphPlot, that = this;
//set alpha to 0 for nodes to remove.
for(var i=0; i<v.length; i++) {
var adjs = viz.graph.getAdjacence(v[i][0], v[i][1]);
if(adjs) {
adjs[0].endAlpha = 0;
adjs[1].endAlpha = 0;
}
}
GPlot.animate(viz, $_.merge(options, {
modes: ['fade:vertex'],
onComplete: function() {
that.removeEdge(viz, v, { type: 'nothing' });
viz.compute('endPos');
GPlot.animate(viz, $_.merge(options, {
modes: ['linear']
}));
}
}));
break;
case 'fade:con':
var GPlot = GraphPlot, that = this;
//set alpha to 0 for nodes to remove. Tag them for being ignored
when computing positions.
for(var i=0; i<v.length; i++) {
var adjs = viz.graph.getAdjacence(v[i][0], v[i][1]);
if(adjs) {
adjs[0].endAlpha = 0;
adjs[0].ignore = true;
adjs[1].endAlpha = 0;
adjs[1].ignore = true;
}
}
viz.compute('endPos');
GPlot.animate(viz, $_.merge(options, {
modes: ['fade:vertex', 'linear'],
onComplete: function() {
that.removeEdge(viz, v, { type: 'nothing' });
}
}));
break;
case 'iter':
var that = this, GPlot = GraphPlot;
GPlot.sequence(viz, {
condition: function() { return v.length != 0; },
step: function() { that.removeEdge(viz, v.shift(), { type:
'nothing' }); GPlot.clearLabels(viz); },
onComplete: function() { options.onComplete(); },
duration: Math.ceil(options.duration / v.length)
});
break;
default: this.doError();
}
},
/*
Method: sum
Adds a new graph to the visualization. The json graph (or tree)
must at least have a common node with the current graph plotted by the
visualization. The resulting graph can be defined as follows: <http://
mathworld.wolfram.com/GraphSum.html>
Parameters:
viz - The visualization object (an RGraph instance in this
case).
json - A json tree <
http://blog.thejit.org/2008/04/27/feeding-
json-tree-structures-to-the-jit/>, a json graph <http://
blog.thejit.org/2008/07/02/feeding-json-graph-structures-to-the-jit/>
or an extended json graph <
http://blog.thejit.org/2008/08/05/weighted-
nodes-weighted-edges/>.
opt - Animation options. It's an object with two properties:
_type_, which can be _nothing_, _replot_, _fade:seq_, or _fade:con_.
The other property is the _duration_ in milliseconds.
*/
sum: function(viz, json, opt) {
var options = $_.merge(viz.controller, this.options, opt), root =
viz.root;
viz.root =
opt.id || viz.root;
switch(options.type) {
case 'nothing':
var graph = viz.construct(json), GUtil = GraphUtil;
GUtil.eachNode(graph, function(elem) {
GUtil.eachAdjacency(elem, function(adj) {
viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
});
});
break;
case 'replot':
this.sum(viz, json, { type: 'nothing' });
viz.refresh();
break;
case 'fade:seq': case 'fade': case 'fade:con':
var GUtil = GraphUtil, GPlot = GraphPlot, that = this, graph =
viz.construct(json);
//set alpha to 0 for nodes to add.
var fadeEdges = this.preprocessSum(viz, graph);
var modes = !fadeEdges? ['fade:nodes'] : ['fade:nodes',
'fade:vertex'];
viz.compute('endPos');
if(options.type != 'fade:con') {
GPlot.animate(viz, $_.merge(options, {
modes: ['linear'],
onComplete: function() {
GPlot.animate(viz, $_.merge(options, {
modes: modes,
onComplete: function() {
options.onComplete();
}
}));
}
}));
} else {
GUtil.eachNode(viz.graph, function(elem) {
if(
elem.id != root && elem.pos.equals(Polar.KER)) elem.pos =
elem.startPos = elem.endPos;
});
GPlot.animate(viz, $_.merge(options, {
modes: ['linear'].concat(modes),
onComplete: function() {
options.onComplete();
}
}));
}
break;
default: this.doError();
}
},
/*
Method: morph
This method will _morph_ the current visualized graph into the new
_json_ representation passed in the method. Can also perform multiple
animations. The _json_ object must at least have the root node in
common with the current visualized graph.
Parameters:
viz - The visualization object (an RGraph instance in this
case).
json - A json tree <
http://blog.thejit.org/2008/04/27/feeding-
json-tree-structures-to-the-jit/>, a json graph <http://
blog.thejit.org/2008/07/02/feeding-json-graph-structures-to-the-jit/>
or an extended json graph <
http://blog.thejit.org/2008/08/05/weighted-
nodes-weighted-edges/>.
opt - Animation options. It's an object with two properties:
_type_, which can be _nothing_, _replot_, or _fade_. The other
property is the _duration_ in milliseconds.
*/
morph: function(viz, json, opt) {
var options = $_.merge(viz.controller, this.options, opt), root =
viz.root;
viz.root =
opt.id || viz.root;
switch(options.type) {
case 'nothing':
var graph = viz.construct(json), GUtil = GraphUtil;
GUtil.eachNode(graph, function(elem) {
GUtil.eachAdjacency(elem, function(adj) {
viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
});
});
GUtil.eachNode(viz.graph, function(elem) {
GUtil.eachAdjacency(elem, function(adj) {
if(!graph.getAdjacence(
adj.nodeFrom.id,
adj.nodeTo.id)) {
viz.graph.removeAdjacence(
adj.nodeFrom.id,
adj.nodeTo.id);
}
if(!viz.graph.hasNode(
elem.id)) viz.graph.removeNode(
elem.id);
});
});
break;
case 'replot':
this.morph(viz, json, { type: 'nothing' });
viz.refresh();
break;
case 'fade:seq': case 'fade': case 'fade:con':
var GUtil = GraphUtil, GPlot = GraphPlot, that = this, graph =
viz.construct(json);
//preprocessing for adding nodes.
var fadeEdges = this.preprocessSum(viz, graph);
//preprocessing for nodes to delete.
GUtil.eachNode(viz.graph, function(elem) {
if(!graph.hasNode(
elem.id)) {
elem.alpha = 1; elem.startAlpha = 1; elem.endAlpha = 0;
elem.ignore = true;
}
});
GUtil.eachNode(viz.graph, function(elem) {
if(elem.ignore) return;
GUtil.eachAdjacency(elem, function(adj) {
if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
var nodeFrom = graph.getNode(
adj.nodeFrom.id);
var nodeTo = graph.getNode(
adj.nodeTo.id);
if(!nodeFrom.adjacentTo(nodeTo)) {
var adjs = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
fadeEdges = true;
adjs[0].alpha = 1; adjs[0].startAlpha = 1; adjs[0].endAlpha =
0; adjs[0].ignore = true;
adjs[1].alpha = 1; adjs[1].startAlpha = 1; adjs[1].endAlpha =
0; adjs[1].ignore = true;
}
});
});
var modes = !fadeEdges? ['fade:nodes'] : ['fade:nodes',
'fade:vertex'];
viz.compute('endPos');
GUtil.eachNode(viz.graph, function(elem) {
if(
elem.id != root && elem.pos.equals(Polar.KER)) elem.pos =
elem.startPos = elem.endPos;
});
GPlot.animate(viz, $_.merge(options, {
modes: ['polar'].concat(modes),
onComplete: function() {
GUtil.eachNode(viz.graph, function(elem) {
if(elem.ignore) viz.graph.removeNode(
elem.id);
});
GUtil.eachNode(viz.graph, function(elem) {
GUtil.eachAdjacency(elem, function(adj) {
if(adj.ignore) viz.graph.removeAdjacence(
adj.nodeFrom.id,
adj.nodeTo.id);
});
});
options.onComplete();
}
}));
break;
default: this.doError();
}
},
preprocessSum: function(viz, graph) {
var GUtil = GraphUtil;
GUtil.eachNode(graph, function(elem) {
if(!viz.graph.hasNode(
elem.id)) {
viz.graph.addNode(elem);
var n = viz.graph.getNode(
elem.id);
n.alpha = 0; n.startAlpha = 0; n.endAlpha = 1;
}
});
var fadeEdges = false;
GUtil.eachNode(graph, function(elem) {
GUtil.eachAdjacency(elem, function(adj) {
var nodeFrom = viz.graph.getNode(
adj.nodeFrom.id);
var nodeTo = viz.graph.getNode(
adj.nodeTo.id);
if(!nodeFrom.adjacentTo(nodeTo)) {
var adjs = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
if(nodeFrom.startAlpha == nodeFrom.endAlpha
&& nodeTo.startAlpha == nodeTo.endAlpha) {
fadeEdges = true;
adjs[0].alpha = 0; adjs[0].startAlpha = 0; adjs[0].endAlpha = 1;
adjs[1].alpha = 0; adjs[1].startAlpha = 0; adjs[1].endAlpha = 1;
}
}
});
});
return fadeEdges;
}
};
/*
Object: GraphPlot
An object that performs specific radial layouts for a generic graph
structure.
*/
var GraphPlot = {
Interpolator: {
'polar': function(elem, delta) {
var from = elem.startPos;
var to = elem.endPos;
elem.pos = to.interpolate(from, delta);
},
'linear': function(elem, delta) {
var from = elem.startPos.toComplex();
var to = elem.endPos.toComplex();
elem.pos = ((to.$add(from.scale(-1))).$scale(delta).$add
(from)).toPolar();
},
'fade:nodes': function(elem, delta) {
if(elem.endAlpha != elem.alpha) {
var from = elem.startAlpha;
var to = elem.endAlpha;
elem.alpha = from + (to - from) * delta;
}
},
'fade:vertex': function(elem, delta) {
var adjs = elem.adjacencies;
for(var id in adjs) this['fade:nodes'](adjs[id], delta);
}
},
//Property: labelsHidden
//A flag value indicating if node labels are being displayed or not.
labelsHidden: false,
//Property: labelContainer
//Label DOM element
labelContainer: false,
//Property: labels
//Label DOM elements hash.
labels: {},
/*
Method: getLabelContainer
Lazy fetcher for the label container.
*/
getLabelContainer: function() {
return this.labelContainer? this.labelContainer :
this.labelContainer = document.getElementById(Config.labelContainer);
},
/*
Method: getLabel
Lazy fetcher for the label DOM element.
*/
getLabel: function(id) {
return (id in this.labels && this.labels[id] != null)? this.labels
[id] : this.labels[id] = document.getElementById(id);
},
/*
Method: hideLabels
Hides all labels.
*/
hideLabels: function (hide) {
var container = this.getLabelContainer();
if(hide) container.style.display = 'none';
else container.style.display = '';
this.labelsHidden = hide;
},
/*
Method: clearLabels
Clears the label container.
*/
clearLabels: function(viz) {
for(var id in this.labels)
if(!viz.graph.hasNode(id)) {
this.disposeLabel(id);
delete this.labels[id];
}
},
/*
Method: disposeLabel
Removes a label.
*/
disposeLabel: function(id) {
var elem = this.getLabel(id);
if(elem && elem.parentNode) {
elem.parentNode.removeChild(elem);
}
},
/*
Method: animate
Animates the graph mantaining a radial layout.
*/
animate: function(viz, opt) {
var that = this, GUtil = GraphUtil, Anim = Animation, duration =
opt.duration || Anim.duration, fps = opt.fps || Anim.fps;
//Should be changed eventually, when Animation becomes a class.
var prevDuration = Anim.duration, prevFps = Anim.fps;
Anim.duration = duration;
Anim.fps = fps;
if(opt.hideLabels) this.hideLabels(true);
var animationController = {
compute: function(delta) {
GUtil.eachNode(viz.graph, function(node) {
for(var i=0; i<opt.modes.length; i++) {
that.Interpolator[opt.modes[i]](node, delta);
}
});
that.plot(viz, opt);
},
complete: function() {
GUtil.eachNode(viz.graph, function(node) {
node.startPos = node.pos;
node.startAlpha = node.alpha;
});
if(opt.hideLabels) that.hideLabels(false);
that.plot(viz, opt);
Anim.duration = prevDuration;
Anim.fps = prevFps;
opt.onComplete();
opt.onAfterCompute();
}
};
Anim.controller = animationController;
Anim.start();
},
/*
Method: sequence
Iteratively performs an action while refreshing the state of the
visualization.
*/
sequence: function(viz, options) {
options = $_.merge({
condition: function() { return false; },
step: $_.fn(),
onComplete: $_.fn(),
duration: 200
}, options);
var interval = setInterval(function() {
if(options.condition()) {
options.step();
} else {
clearInterval(interval);
options.onComplete();
}
viz.refresh();
}, options.duration);
},
/*
Method: plot
Plots a Graph.
*/
plot: function(viz, opt) {
var aGraph = viz.graph, canvas = viz.canvas, id = viz.root;
var that = this, ctx = canvas.getContext(), GUtil = GraphUtil;
canvas.clear();
if(Config.drawConcentricCircles) canvas.drawConcentricCircles
(Config.drawConcentricCircles);
var T = !!aGraph.getNode(id).visited;
GUtil.eachNode(aGraph, function(node) {
GUtil.eachAdjacency(node, function(adj) {
if(!!adj.nodeTo.visited === T) {
opt.onBeforePlotLine(adj);
ctx.save();
ctx.globalAlpha = Math.min(Math.min(node.alpha,
adj.nodeTo.alpha), adj.alpha);
that.plotLine(adj, canvas);
ctx.restore();
opt.onAfterPlotLine(adj);
}
});
ctx.save();
ctx.globalAlpha = node.alpha;
that.plotNode(node, canvas);
if(!that.labelsHidden && ctx.globalAlpha >= .95) that.plotLabel
(canvas, node, opt);
else if(!that.labelsHidden && ctx.globalAlpha < .95) that.hideLabel
(node);
ctx.restore();
node.visited = !T;
});
},
/*
Method: plotNode
Plots a graph node.
*/
plotNode: function(node, canvas) {
var pos = node.pos.toComplex();
canvas.path('fill', function(context) {
context.arc(pos.x, pos.y, node._radius, 0, Math.PI*2, true);
});
},
/*
Method: plotLine
Plots a line connecting _node_ and _child_ nodes.
*/
plotLine: function(adj, canvas) {
var node = adj.nodeFrom, child = adj.nodeTo;
var pos = node.pos.toComplex();
var posChild = child.pos.toComplex();
canvas.path('stroke', function(context) {
context.moveTo(pos.x, pos.y);
context.lineTo(posChild.x, posChild.y);
});
},
/*
Method: hideLabel
Hides a label having _node.id_ as id.
*/
hideLabel: function(node) {
var n; if(n = document.getElementById(
node.id)) n.style.display =
"none";
},
/*
Method: plotLabel
Plots a label for a given node.
*/
plotLabel: function(canvas, node, controller) {
var size = node._radius, id =
node.id, tag = this.getLabel(id);
if(!tag && !(tag = document.getElementById(id))) {
tag = document.createElement('div');
var container = this.getLabelContainer();
container.appendChild(tag);
tag.id = id;
tag.className = 'node';
tag.style.position = 'absolute';
controller.onCreateLabel(tag, node);
}
var pos = node.pos.toComplex();
var radius= canvas.getSize();
var cpos = canvas.getPosition();
var labelPos= {
x: Math.round(pos.x + cpos.x + radius.x/2 - size /2),
y: Math.round(pos.y + cpos.y + radius.y/2 - size /2)
};
tag.style.width = size + 'px';
tag.style.height = size + 'px';
tag.style.left = labelPos.x + 'px';
tag.style.top = labelPos.y + 'px';
tag.style.display = this.fitsInCanvas(labelPos, canvas)? '' :
'none';
controller.onPlaceLabel(tag, node);
},
/*
Method: fitsInCanvas
Returns _true_ or _false_ if the label for the node is contained
on the canvas dom element or not.
*/
fitsInCanvas: function(pos, canvas) {
var size = canvas.getSize();
if(pos.x >= size.x + canvas.position.x || pos.x < canvas.position.x
|| pos.y >= size.y + canvas.position.y || pos.y <
canvas.position.y) return false;
return true;
}
};
/*
Class: RGraph
An animated Graph with radial layout.
Go to <
http://blog.thejit.org> to know what kind of JSON structure
feeds this object.
Go to <
http://blog.thejit.org/?p=8> to know what kind of controller
this class accepts.
Refer to the <Config> object to know what properties can be modified
in order to customize this object.
The simplest way to create and layout a RGraph is:
(start code)
var canvas= new Canvas('infovis', '#ccddee', '#772277');
var rgraph= new RGraph(canvas, controller);
rgraph.loadTreeFromJSON(json);
rgraph.compute();
rgraph.plot();
(end code)
A user should only interact with the Canvas, RGraph and Config
objects/classes.
By implementing RGraph controllers you can also customize the RGraph
behavior.
*/
/*
Constructor: RGraph
Creates a new RGraph instance.
Parameters:
canvas - A <Canvas> instance.
controller - _optional_ a RGraph controller <http://
blog.thejit.org/?p=8>
*/
var RGraph = function(canvas, controller) {
var innerController = {
onBeforeCompute: $_.fn(),
onAfterCompute: $_.fn(),
onCreateLabel: $_.fn(),
onPlaceLabel: $_.fn(),
onCreateElement: $_.fn(),
onComplete: $_.fn(),
onBeforePlotLine: $_.fn(),
onAfterPlotLine: $_.fn(),
request: false
};
this.controller = $_.merge(innerController, controller);
this.graph = new Graph();
this.json = null;
this.canvas = canvas;
this.root = null;
this.busy = false;
Animation.duration = Config.animationTime;
Animation.fps = Config.fps;
};
RGraph.prototype = {
construct: function(json) {
var isGraph = $_.isArray(json);
var ans = new Graph();
if(!isGraph)
//make tree
(function (ans, json) {
ans.addNode(json);
for(var i=0, ch = json.children; i<ch.length; i++) {
ans.addAdjacence(json, ch[i]);
arguments.callee(ans, ch[i]);
}
})(ans, json);
else
//make graph
(function (ans, json) {
var getNode = function(id) {
for(var w=0; w<json.length; w++) if(json[w].id == id) return json
[w];
};
for(var i=0; i<json.length; i++) {
ans.addNode(json[i]);
for(var j=0, adj = json[i].adjacencies; j<adj.length; j++) {
var node = adj[j], data;
if(typeof adj[j] != 'string') {
data = node.data;
node = node.nodeTo;
}
ans.addAdjacence(json[i], getNode(node), data);
}
}
})(ans, json);
return ans;
},
/*
Method: loadTree
Loads a Graph from a json tree object <
http://blog.thejit.org>
*/
loadTree: function(json) {
this.graph = this.construct(json);
},
/*
Method: loadGraph
Loads a Graph from a json graph object <
http://blog.thejit.org>
*/
loadGraph: function(json) {
this.graph = this.construct(json);
},
/*
Method: refresh
Computes positions and then plots.
*/
refresh: function() {
this.compute();
this.plot();
},
/*
Method: flagRoot
Flags a node specified by _id_ as root.
*/
flagRoot: function(id) {
this.unflagRoot();
this.graph.nodes[id]._root = true;
},
/*
Method: unflagRoot
Unflags all nodes.
*/
unflagRoot: function() {
GraphUtil.eachNode(this.graph, function(elem) {elem._root =
false;});
},
/*
Method: getRoot
Returns the node flagged as root.
*/
getRoot: function() {
var root = false;
GraphUtil.eachNode(this.graph, function(elem){ if(elem._root) root =
elem; });
return root;
},
/*
Method: loadTreeFromJSON
Loads a RGraph from a _json_ object <
http://blog.thejit.org>
*/
loadTreeFromJSON: function(json) {
this.json = json;
this.loadTree(json);
this.root =
json.id;
},
/*
Method: loadGraphFromJSON
Loads a RGraph from a _json_ object <
http://blog.thejit.org>
*/
loadGraphFromJSON: function(json, i) {
this.json = json;
this.loadGraph(json);
this.root = json[i? i : 0].id;
},
/*
Method: plot
Plots the RGraph
*/
plot: function() {
GraphPlot.plot(this, this.controller);
},
/*
Method: compute
Computes the graph nodes positions and stores this positions on
_property_.
*/
compute: function(property) {
var prop = property || ['pos', 'startPos', 'endPos'];
var node = this.graph.getNode(this.root);
node._depth = 0;
this.flagRoot(this.root);
GraphUtil.computeLevels(this.graph, this.root, "ignore");
this.computeAngularWidths();
this.computePositions(prop);
},
/*
Method: computePositions
Performs the main algorithm for computing node positions.
*/
computePositions: function(property) {
var propArray = (typeof property == 'array' || typeof property ==
'object')? property : [property];
var aGraph = this.graph;
var GUtil = GraphUtil;
var root = this.graph.getNode(this.root);
for(var i=0; i<propArray.length; i++)
root[propArray[i]] = new Polar(0, 0);
root.angleSpan = {
begin: 0,
end: 2 * Math.PI
};
root._rel = 1;
GUtil.eachBFS(this.graph, this.root, function (elem) {
var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
var rho = (elem._depth + 1) * Config.levelDistance;
var angleInit = elem.angleSpan.begin;
var totalAngularWidths = (function (element){
var total = 0;
GUtil.eachSubnode(aGraph, element, function(sib) {
total += sib._treeAngularWidth;
}, "ignore");
return total;
})(elem);
GUtil.eachSubnode(aGraph, elem, function(child) {
if(!child._flag) {
child._rel = child._treeAngularWidth / totalAngularWidths;
var angleProportion = child._rel * angleSpan;
var theta = angleInit + angleProportion / 2;
for(var i=0; i<propArray.length; i++)
child[propArray[i]] = new Polar(theta, rho);
child.angleSpan = {
begin: angleInit,
end: angleInit + angleProportion
};
angleInit += angleProportion;
}
}, "ignore");
}, "ignore");
},
/*
Method: setAngularWidthForNodes
Sets nodes angular widths.
*/
setAngularWidthForNodes: function() {
var rVal = Config.nodeRangeValues, rDiam =
Config.nodeRangeDiameters, nr = Config.nodeRadius, allow =
Config.allowVariableNodeDiameters;
var diam = function(value) { return (((rDiam.max - rDiam.min)/
(rVal.max - rVal.min)) * (value - rVal.min) + rDiam.min) };
GraphUtil.eachBFS(this.graph, this.root, function(elem, i) {
var dataValue = (allow && elem.data && elem.data.length > 0)?
elem.data[0].value : nr;
var diamValue = diam(dataValue);
var rho = Config.levelDistance * i;
elem._angularWidth = diamValue / rho;
elem._radius = allow? diamValue / 2 : nr;
}, "ignore");
},
/*
Method: setSubtreesAngularWidths
Sets subtrees angular widths.
*/
setSubtreesAngularWidth: function() {
var that = this;
GraphUtil.eachNode(this.graph, function(elem) {
that.setSubtreeAngularWidth(elem);
}, "ignore");
},
/*
Method: setSubtreeAngularWidth
Sets the angular width for a subtree.
*/
setSubtreeAngularWidth: function(elem) {
var that = this, nodeAW = elem._angularWidth, sumAW = 0;
GraphUtil.eachSubnode(this.graph, elem, function(child) {
that.setSubtreeAngularWidth(child);
sumAW += child._treeAngularWidth;
}, "ignore");
elem._treeAngularWidth = Math.max(nodeAW, sumAW);
},
/*
Method: computeAngularWidths
Computes nodes and subtrees angular widths.
*/
computeAngularWidths: function () {
this.setAngularWidthForNodes();
this.setSubtreesAngularWidth();
},
/*
Method: getNodeAndParentAngle
Returns the _parent_ of the given node, also calculating its angle
span.
*/
getNodeAndParentAngle: function(id) {
var theta = false;
var n = this.graph.getNode(id);
var ps = GraphUtil.getParents(this.graph, n);
var p = (ps.length > 0)? ps[0] : false;
if(p) {
var posParent = p.pos.toComplex(), posChild = n.pos.toComplex();
var newPos = posParent.add(posChild.scale(-1));
theta = (function(pos) {
var t = Math.atan2(pos.y, pos.x);
if(t < 0) t = 2 * Math.PI + t;
return t;
})(newPos);
}
return {_parent: p, theta: theta};
},
/*
Method: onClick
Performs all calculations and animation when clicking on a label
specified by _id_. The label id is the same id as its homologue node.
*/
onClick: function(id) {
if(this.root != id && !this.busy) {
this.busy = true;
//we apply first constraint to the algorithm
var obj = this.getNodeAndParentAngle(id);
this.root = id, that = this;
this.controller.onBeforeCompute(this.graph.getNode(id));
console.log("!");
var thetaDiff = obj.theta - obj._parent.endPos.theta;
GraphUtil.eachNode(this.graph, function(elem) {
elem.endPos = elem.endPos.add(new Polar(thetaDiff, 0));
});
var mode = (Config.interpolation == 'linear')? 'linear' : 'polar';
GraphPlot.animate(this, $_.merge(this.controller, {
hideLabels:true,
modes: [mode],
onComplete: function() {
that.busy = false;
}
}));
}
}
};
/*
Class: Graph
A generic Graph class.
*/
/*
Constructor: Graph
Creates a new Graph instance.
*/
var Graph= function() {
//Property: nodes
//graph nodes
this.nodes= {};
};
Graph.prototype= {
/*
Method: getNode
Returns a <Graph.Node> from a specified _id_.
*/
getNode: function(id) {
if(this.hasNode(id)) return this.nodes[id];
return false;
},
/*
Method: getAdjacence
Returns an array of <Graph.Adjacence> that connects nodes with id
_id_ and _id2_.
*/
getAdjacence: function (id, id2) {
var adjs = [];
if(this.hasNode(id) && this.hasNode(id2)
&& this.nodes[id].adjacentTo({ 'id':id2 }) && this.nodes
[id2].adjacentTo({ 'id':id })) {
adjs.push(this.nodes[id].getAdjacency(id2));
adjs.push(this.nodes[id2].getAdjacency(id));
return adjs;
}
return false;
},
/*
Method: addNode
Adds a node.
Parameters:
obj - A <Graph.Node> object.
*/
addNode: function(obj) {
if(!this.nodes[
obj.id]) {
this.nodes[
obj.id] = new Graph.Node(
obj.id,
obj.name, obj.data);
}
return this.nodes[
obj.id];
},
/*
Method: addAdjacence
Connects nodes specified by *obj* and *obj2*. If not found, nodes
are created.
Parameters:
obj - a <Graph.Node> object.
obj2 - Another <Graph.Node> object.
data - A DataSet object.
*/
addAdjacence: function (obj, obj2, weight) {
var adjs = []
if(!this.hasNode(
obj.id)) this.addNode(obj);
if(!this.hasNode(
obj2.id)) this.addNode(obj2);
obj = this.nodes[
obj.id]; obj2 = this.nodes[
obj2.id];
for(var i in this.nodes) {
if(this.nodes[i].id ==
obj.id) {
if(!this.nodes[i].adjacentTo(obj2)) {
adjs.push(this.nodes[i].addAdjacency(obj2, weight));
}
}
if(this.nodes[i].id ==
obj2.id) {
if(!this.nodes[i].adjacentTo(obj)) {
adjs.push(this.nodes[i].addAdjacency(obj, weight));
}
}
}
return adjs;
},
/*
Method: removeNode
Removes a <Graph.Node> from <Graph> that matches the specified _id_.
*/
removeNode: function(id) {
if(this.hasNode(id)) {
var node = this.nodes[id];
for(var i=0 in node.adjacencies) {
var adj = node.adjacencies[i];
this.removeAdjacence(id,
adj.nodeTo.id);
}
delete this.nodes[id];
}
},
/*
Method: removeAdjacence
Removes a <Graph.Adjacence> from <Graph> that matches the specified
_id1_ and _id2_.
*/
removeAdjacence: function(id1, id2) {
if(this.hasNode(id1)) this.nodes[id1].removeAdjacency(id2);
if(this.hasNode(id2)) this.nodes[id2].removeAdjacency(id1);
},
/*
Method: hasNode
Returns a Boolean instance indicating if node belongs to graph or
not.
Parameters:
id - Node id.
Returns:
A Boolean instance indicating if node belongs to graph or not.
*/
hasNode: function(id) {
return id in this.nodes;
}
};
/*
Class: Graph.Node
Behaviour of the <Graph> node.
*/
/*
Constructor: Graph.Node
Node constructor.
Parameters:
id - The node *unique identifier* id.
name - A node's name.
data - Place to store some extra information (can be left to
null).
Returns:
A new <Graph.Node> instance.
*/
Graph.Node = function(id, name, data) {
//Property: id
//A node's id
this.id= id;
//Property: name
//A node's name
this.name = name;
//Property: data
//The dataSet object <
http://blog.thejit.org/?p=7>
this.data = data;
//Property: drawn
//Node flag
this.drawn= false;
//Property: angle span
//allowed angle span for adjacencies placement
this.angleSpan= {
begin:0,
end:0
};
//Property: pos
//node position
this.pos= new Polar(0, 0);
//Property: startPos
//node from position
this.startPos= new Polar(0, 0);
//Property: endPos
//node to position
this.endPos= new Polar(0, 0);
//Property: alpha
//node alpha
this.alpha = 1;
//Property: startAlpha
//node start alpha
this.startAlpha = 1;
//Property: endAlpha
//node end alpha
this.endAlpha = 1;
//Property: adjacencies
//node adjacencies
this.adjacencies= {};
};
Graph.Node.prototype= {
/*
Method: adjacentTo
Indicates if the node is adjacent to the node indicated by the
specified id
Parameters:
id - A node id.
Returns:
A Boolean instance indicating whether this node is adjacent to
the specified by id or not.
*/
adjacentTo: function(node) {
return
node.id in this.adjacencies;
},
/*
Method: getAdjacency
Returns a <Graph.Adjacence> that connects the current <Graph.Node>
with the node having _id_ as id.
Parameters:
id - A node id.
*/
getAdjacency: function(id) {
return this.adjacencies[id];
},
/*
Method: addAdjacency
Connects the node to the specified by id.
Parameters:
id - A node id.
*/
addAdjacency: function(node, data) {
var adj = new Graph.Adjacence(this, node, data);
return this.adjacencies[
node.id] = adj;
},
/*
Method: removeAdjacency
Deletes the <Graph.Adjacence> by _id_.
Parameters:
id - A node id.
*/
removeAdjacency: function(id) {
delete this.adjacencies[id];
}
};
/*
Class: Graph.Adjacence
Creates a new <Graph> adjacence.
*/
Graph.Adjacence = function(nodeFrom, nodeTo, data) {
//Property: nodeFrom
//One of the two <Graph.Node>s connected by this edge.
this.nodeFrom = nodeFrom;
//Property: nodeTo
//One of the two <Graph.Node>s connected by this edge.
this.nodeTo = nodeTo;
//Property: data
//A dataset object
this.data = data;
//Property: alpha
//node alpha
this.alpha = 1;
//Property: startAlpha
//node start alpha
this.startAlpha = 1;
//Property: endAlpha
//node end alpha
this.endAlpha = 1;
};
/*
Object: Trans
An object containing multiple type of transformations. Based on the
mootools library <
http://mootools.net>.
*/
var Trans = {
linear: function(p) { return p; },
Quart: function(p) {
return Math.pow(p, 4);
},
easeIn: function(transition, pos){
return transition(pos);
},
easeOut: function(transition, pos){
return 1 - transition(1 - pos);
},
easeInOut: function(transition, pos){
return (pos <= 0.5) ? transition(2 * pos) / 2 : (2 - transition(2 *
(1 - pos))) / 2;
}
};
/*
Object: Animation
An object that performs animations. Based on Fx.Base from Mootools.
*/
var Animation = {
duration: Config.animationTime,
fps: Config.fps,
transition: function(p) {return Trans.easeInOut(Trans.Quart, p);},
//transition: Trans.linear,
controller: false,
getTime: function() {
var ans = (Date.now)? Date.now() : new Date().getTime();
return ans;
},
step: function(){
var time = this.getTime();
if (time < this.time + this.duration){
var delta = this.transition((time - this.time) / this.duration);
this.controller.compute(delta);
} else {
this.timer = clearInterval(this.timer);
this.controller.compute(1);
this.controller.complete();
}
},
start: function(){
this.time = 0;
this.startTimer();
return this;
},
startTimer: function(){
if (this.timer) return false;
this.time = this.getTime() - this.time;
this.timer = setInterval((function () { Animation.step(); }),
Math.round(1000 / this.fps));
return true;
}
};
/*Jon's JIT Hacks */
Config.animationTime = 1000;
/*RGRAPH*/
RGraph.prototype.offsetCenter= function(x,y){
var d =this.controller.getOffset();
d.x = x;
d.y = y;
this.controller.setOffset(d);
};
RGraph.prototype.setAngularWidthForNodes= function() {
var rVal = Config.nodeRangeValues, rDiam = Config.nodeRangeDiameters,
nr = Config.nodeRadius, allow = Config.allowVariableNodeDiameters;
var zoom = this.controller.getZoomLevel();
var diam = function(value) { return (((rDiam.max - rDiam.min)/
(rVal.max - rVal.min)) * (value - rVal.min) + rDiam.min) };
GraphUtil.eachBFS(this.graph, this.root, function(elem, i) {
var dataValue = (allow && elem.data && elem.data.length > 0)?
elem.data[0].value : nr;
var diamValue = diam(dataValue);
var rho = zoom * i;//jon
elem._angularWidth = diamValue / rho;
elem._radius = allow? diamValue / 2 : nr;
}, "ignore");
};
RGraph.prototype.computePositions= function(property) {
var propArray = (typeof property == 'array' || typeof property ==
'object')? property : [property];
var aGraph = this.graph;
var GUtil = GraphUtil;
var root = this.graph.getNode(this.root);
for(var i=0; i<propArray.length; i++)
root[propArray[i]] = new Polar(0, 0);
root.angleSpan = {
begin: 0,
end: 2 * Math.PI
};
root._rel = 1;
var zoom =this.controller.getZoomLevel();
GUtil.eachBFS(this.graph, this.root, function (elem) {
var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
var rho = (elem._depth + 1) * zoom;//jon
var angleInit = elem.angleSpan.begin;
var totalAngularWidths = (function (element){
var total = 0;
GUtil.eachSubnode(aGraph, element, function(sib) {
total += sib._treeAngularWidth;
}, "ignore");
return total;
})(elem);
GUtil.eachSubnode(aGraph, elem, function(child) {
if(!child._flag) {
child._rel = child._treeAngularWidth / totalAngularWidths;
var angleProportion = child._rel * angleSpan;
var theta = angleInit + angleProportion / 2;
for(var i=0; i<propArray.length; i++)
child[propArray[i]] = new Polar(theta, rho);
child.angleSpan = {
begin: angleInit,
end: angleInit + angleProportion
};
angleInit += angleProportion;
}
}, "ignore");
}, "ignore");
};
RGraph.prototype.onClick= function(id) { //weird bug in here
if(this.root != id) {//jon
this.busy = true;
//we apply first constraint to the algorithm
var obj = this.getNodeAndParentAngle(id);
this.root = id, that = this;
this.controller.onBeforeCompute(this.graph.getNode(id));
this.compute('endPos');
var thetaDiff = obj.theta - obj._parent.endPos.theta;
GraphUtil.eachNode(this.graph, function(elem) {
elem.endPos = elem.endPos.add(new Polar(thetaDiff, 0));
});
var mode = (Config.interpolation == 'linear')? 'linear' : 'polar';
this.controller.modes = [mode];//jon
GraphPlot.animate(this, this.controller);//jon
}
};
/*GRAPH PLOT */
GraphPlot.plotLine = function(adj, canvas,controller) {//jon
var d = controller.getOffset();//jon
var node = adj.nodeFrom, child = adj.nodeTo;
var pos = node.pos.toComplex();
var posChild = child.pos.toComplex();
canvas.path('stroke', function(context) {
pos.x += d.x;//jon..
pos.y += d.y;
posChild.x += d.x;
posChild.y += d.y;//..jon
context.moveTo(pos.x, pos.y);
context.lineTo(posChild.x, posChild.y);
});
};
GraphPlot.getLabelContainer = function(controller){
return document.getElementById(controller.getNodeLabelContainer());
};
GraphPlot.fitsInCanvas= function(pos, canvas) {
//canvas.setPosition();
var size = canvas.getSize();
var offset1 = parseInt(size.x);
var offset2 = parseInt(size.y);
if(pos.x < 0 || pos.x > offset1 || pos.y < 0 || pos.y > offset2)
return false;
else
return true;
};
GraphPlot.plotLabel= function(canvas, node, controller) {
var size = node._radius;
var id = controller.getNodeLabelPrefix() +
node.id;//jon change
var d = controller.getOffset(); //jon
var pos = node.pos.toComplex();
var radius= canvas.getSize();
canvas.setPosition(); //jon
var cpos = canvas.getPosition();
var labelPos= {
x: Math.round((pos.x + radius.x/2 - size /2) +d.x),//jon
y: Math.round((pos.y + radius.y/2 - size /2) +d.y)//jon
};
var tag = this.getLabel(id);
if(!this.fitsInCanvas(labelPos,canvas)) {
//if(tag && tag.parentNode)tag.parentNode.removeChild(tag);
return;
}
if(!tag && !(tag = document.getElementById(id))) {
tag = document.createElement('div');
var container = this.getLabelContainer(controller); //jon change
container.style.position= 'relative';//jon change
container.appendChild(tag);
tag.id = id;
tag.className = 'node';
tag.style.position = 'absolute';
controller.onCreateLabel(tag, node);
}
tag.style.width = size + 'px';
tag.style.height = size + 'px';
tag.style.left = labelPos.x + 'px';
tag.style.top = labelPos.y + 'px';
tag.style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
controller.onPlaceLabel(tag, node);
};
/*overriding of several functions */
GraphPlot.plotNode = function(node, canvas,controller) {
var pos = node.pos.toComplex();
var d = controller.getOffset();
if(node.data.nodraw == undefined) //jon
canvas.path('fill', function(context) {
context.arc(pos.x +d.x, pos.y +d.y, node._radius, 0, Math.PI*2,
true); //jon
});
};
GraphPlot.plot= function(viz, opt) {
var aGraph = viz.graph, canvas = viz.canvas, id = viz.root;
var controller = viz.controller;//jon
var container = this.getLabelContainer(controller); //jon change
container.innerHTML = "";
var that = this, ctx = canvas.getContext(), GUtil = GraphUtil;
canvas.clear();
if(Config.drawConcentricCircles) canvas.drawConcentricCircles
(Config.drawConcentricCircles);
var T = !!aGraph.getNode(id).visited;
GUtil.eachNode(aGraph, function(node) {
GUtil.eachAdjacency(node, function(adj) {
if(!!adj.nodeTo.visited === T) {
opt.onBeforePlotLine(adj);
ctx.save();
ctx.globalAlpha = Math.min(Math.min(node.alpha, adj.nodeTo.alpha),
adj.alpha);
that.plotLine(adj, canvas,controller);//jon
ctx.restore();
opt.onAfterPlotLine(adj);
}
});
ctx.save();
ctx.globalAlpha = node.alpha;
that.plotNode(node, canvas,controller); //jon
if(!that.labelsHidden && ctx.globalAlpha >= .95) that.plotLabel
(canvas, node, opt);
else if(!that.labelsHidden && ctx.globalAlpha < .95) that.hideLabel
(node);
ctx.restore();
node.visited = !T;
});
};
//georss support please
var EasyShape = function(properties,coordinates,geojson){
this.grid = {};
this.coords = [];
if(geojson){
this._constructFromGeoJSONObject(properties,coordinates);
}
else{
this._constructBasicShape(properties,coordinates);
}
this._iemultiplier = 1000; //since vml doesn't accept floats you have
to define the precision of your points 100 means you can get float
coordinates 0.01 and 0.04 but not 0.015 and 0.042 etc..
};
EasyShape.prototype={
_calculateBounds: function(coords){
if(this.properties.shape == 'path' | this.properties.shape =='text')
{
this.grid = {x1:0,x2:1,y1:0,y2:1};
return;
}
if(!coords) coords = this.coords;
this.grid.x1 = coords[0];
this.grid.y1 = coords[1];
this.grid.x2 = coords[0];
this.grid.y2 = coords[1];
this._deltas = []
var d = this._deltas;
var lastX, lastY;
var index = 0;
lastX = coords[0];
lastY = coords[1];
for(var i=0; i < coords.length-1; i+=2){
var xPos = parseFloat(coords[i]); //long
var yPos = parseFloat(coords[i+1]); //lat
var deltax =xPos - lastX;
var deltay= yPos - lastY;
if(deltax < 0) deltax = - deltax;
if(deltay < 0) deltay = -deltay;
d.push(deltax);
d.push(deltay);
if(xPos < this.grid.x1) this.grid.x1 = xPos;
if(yPos < this.grid.y1) this.grid.y1 = yPos;
if(xPos > this.grid.x2) this.grid.x2 = xPos;
if(yPos > this.grid.y2) this.grid.y2 = yPos;
lastX = xPos;
lastY = yPos;
}
}
,setCoordinates: function(coordinates){
this.coords = coordinates;
this.grid = {}; //an enclosing grid
this._calculateBounds();
if(this.vml) this.vml.path = false; //reset path so recalculation
will occur
}
,_constructPointShape: function(properties,coordinates){
var x = coordinates[0]; var y = coordinates[1];
this.pointcoords = [x,y];
var ps = 0.5;
var newcoords =[x-ps,y-ps,x+ps,y-ps,x+ps,y+ps,x-ps, y+ps];
this._constructPolygonShape(properties,newcoords);
}
,_constructTextShape: function(properties,coordinates){
this.properties = properties;
var t = document.createElement("div");
t.className = "easyShape";
t.style.position = "absolute";
this._textLabel = t;
this.setCoordinates(coordinates);
}
,_constructPolygonShape: function(properties,coordinates){
this.properties = properties;
this.setCoordinates(coordinates);
if(!properties.stroke)properties.stroke = '#000000';
if(properties.colour){
properties.fill = properties.colour;
}
}
,_constructBasicShape: function(properties, coordinates){
if(properties.shape == 'text'){
this._constructTextShape(properties,coordinates);
}
else if(properties.shape == 'point'){
this._constructPointShape(properties,coordinates);
}
else if(properties.shape == 'polygon' || properties.shape == 'path')
{
this._constructPolygonShape(properties,coordinates);
}
else{
console.log("don't know how to construct basic shape " +
properties.shape);
}
}
/*following 3 functions may be better in EasyMaps*/
,_constructFromGeoJSONObject: function(properties,coordinates){
if(properties.shape == 'polygon'){
this._constructFromGeoJSONPolygon(properties,coordinates);
}
else if(properties.shape == 'point'){
this._constructPointShape(properties,coordinates);
}
else{
console.log("don't know what to do with shape " + element.shape);
}
}
,_constructFromGeoJSONPolygon: function(properties,coordinates){
var newcoords = this._convertGeoJSONCoords(coordinates[0]);
this._constructBasicShape(properties,newcoords);
//we ignore any holes in the polygon (for time being.. coords[1]
[0..n], coords[2][0..n])
}
,_convertGeoJSONCoords: function(coords){
//converts [[x1,y1], [x2,y2],...[xn,yn]] to [x1,y1,x2,y2..xn,yn]
var res = [];
if(!coords) return res;
for(var i=0; i < coords.length; i++){
//geojson says coords order should be longitude,latitude eg. 0,51
for London
// longitude goes from -180 (W) to 180 (E), latitude from -90 (S)
to 90 (N)
// in our data, lat goes from 90 (S) to -90 (N), so we negate
var x = coords[i][0];
var y = - coords[i][1];
//var y = -coords[i][0];
//var x = coords[i][1];
res.push(x);
res.push(y);
}
return res;
}
/*RENDERING */
,_renderTextShape: function(canvas,transformation){
var t =this._textLabel;
var coordinates = this.coords;
var x= coordinates[0];
var y =coordinates[1];
t.innerHTML =
this.properties.name;
if(t.parentNode == null){
t.style.left = parseInt(x) +"px";
t.style.top = parseInt(y)+"px";
t.style.width = "200px";
t.style.height = "200px";
t.style.textAlign = "center";
canvas.appendChild(this._textLabel);
}
this._cssTransform(t,transformation,false);
t.style.lineHeight = t.style.height;
}
,_canvasrender: function
(canvas,transformation,projection,optimisations){
var c;
var shapetype = this.properties.shape;
if(projection)
c = this._applyProjection(projection,transformation);
else
c = this.coords;
if(c.length == 0) return;
var initialX,initialY;
if(c[0] == 'M'){//starts with an "M"
initialX = parseFloat(c[1]);
initialY = parseFloat(c[2]);
}
else{
initialX = parseFloat(c[0]);
initialY = parseFloat(c[1]);
}
var threshold = 2;
var ctx = canvas.getContext('2d');
if(this.properties.lineWidth) ctx.lineWidth =
this.properties.lineWidth;
var o = transformation.origin;
var tr = transformation.translate;
var s = transformation.scale;
var r = transformation.rotate;
ctx.save();
ctx.translate(o.x,o.y);
ctx.scale(s.x,s.y);
ctx.translate(tr.x,tr.y);
//if(r && r.x)ctx.rotate(r.x,o.x,o.y);
ctx.beginPath();
ctx.moveTo(initialX,initialY);
var move;
for(var i=2; i < c.length-1; i+=2){
if(c[i]== "M") {
i+= 1;
move=true;
}
var x = parseFloat(c[i]);
var y = parseFloat(c[i+1]);
if(x == NaN || y == NaN){
throw "error in EasyShape render: the coordinates for this
EasyShape contain invalid numbers";
}
else{
if(move){
ctx.moveTo(x,y);
move = false;
}
else{
ctx.lineTo(x,y);
}
}
}
//connect last to first
//if(shapetype != 'path') ctx.lineTo(initialX,initialY);
ctx.closePath();
if(!this.properties.hidden) {
ctx.strokeStyle = this.properties.stroke;
if(typeof this.properties.fill == 'string')
fill = this.properties.fill;
else
fill = "#ffffff";
ctx.stroke();
if(shapetype != 'path') {
ctx.fillStyle = fill;
ctx.fill();
}
}
ctx.restore();
}
,_createvmlpathstring: function(vml,transformation,projection){ //mr
bottleneck
if(!vml) return;
var o = transformation.origin;
var t = transformation.translate;
var s = transformation.scale;
var path;
var buffer = [];
if(projection){
c = this._applyProjection(projection,transformation);
}
else{
c = this.coords;
}
if(c.length < 2) return;
var x =o.x + c[0];
var y =o.y+c[1];
x *=this._iemultiplier;
y *= this._iemultiplier;
x = parseInt(x);
y = parseInt(y);
//path = "M";
buffer.push("M");
//path+= x + "," +y + " L";
buffer.push([x,",",y," L"].join(""))
var lineTo = true;
for(var i =2; i < c.length; i+=2){
if(c[i] == 'M') {
//path += " M";
buffer.push(" M");
lineTo = false;
i+=1;
}
else if(!lineTo) {
//path += " L";
buffer.push(" L");
lineTo = true;
}
else if(lineTo){
//path += " ";
buffer.push(" ");
}
var x =o.x+c[i];
var y =o.y+c[i+1];
x *= this._iemultiplier;
y *= this._iemultiplier;
x = parseInt(x);
y = parseInt(y);
buffer.push([x, ",",y].join(""));
//path += x +"," + y;
//if(i < c.length - 2) path += "";
}
//path += " XE";
buffer.push(" XE");
//console.log(buffer.join(""));
path = buffer.join("");
//if(path != vml.getAttribute("path")){
vml.setAttribute("path", path);
// }
}
,_cssTransform: function(vml,transformation,projection){
var d1,d2,t;
if(!vml) return;
if(vml.tagName == 'shape' && (!vml.path || this.properties.shape
=='point' ||projection)) {
//causes slow down..
this._createvmlpathstring(vml,transformation,projection);
// this.vml.parentNode.replaceChild(clonedNode,this.vml);
}
var o = transformation.origin;
var t = transformation.translate;
var s = transformation.scale;
if(!this.initialStyle) {
var initTop = parseInt(vml.style.top);
if(!initTop) initTop = 0;
initTop += o.y;
var initLeft = parseInt(vml.style.left);
if(!initLeft) initLeft = 0;
initLeft += o.x;
var w =parseInt(vml.style.width);
var h = parseInt(vml.style.height)
this.initialStyle = {top: initTop, left: initLeft, width: w,
height: h};
}
var scalingRequired = true;
var translatingRequired = true;
if(this._lastTransformation){
if(s.x == this._lastTransformation.scale.x && s.y ==
this._lastTransformation.scale.y){
scalingRequired = false;
}
}
var initialStyle= this.initialStyle;
var style = vml.style;
var newtop,newleft;
newtop = initialStyle.top;
newleft = initialStyle.left;
//scale
if(scalingRequired){
var newwidth = initialStyle.width * s.x;
var newheight = initialStyle.height * s.y;
}
//translate into right place
var temp;
temp = (t.x - o.x);
temp *= s.x;
newleft += temp;
temp = (t.y - o.y);
temp *= s.x;
newtop += temp;
style.left = newleft +"px";
style.top = newtop +"px";
if(scalingRequired){
style.width = newwidth +"px";
style.height = newheight + "px";
}
this._lastTransformation = {scale:{}};
this._lastTransformation.scale.x = s.x;
this._lastTransformation.scale.y = s.y;
}
,_ierender: function
(canvas,transformation,projection,optimisations,appendTo){
var shape;
if(this.vml){
shape = this.vml;
if(this.properties.fill && shapetype != 'path'){
shape.filled = "t";
shape.fillcolor = this.properties.fill;
}
this._cssTransform(shape,transformation,projection);
return;
}
else{
shape = document.createElement("g_vml_:shape");
var o = transformation.origin;
var t = transformation.translate;
var s = transformation.scale;
//path ="M 0,0 L50,0, 50,50, 0,50 X";
var nclass= "easyShape";
var shapetype =this.properties.shape;
if(shapetype == 'path') nclass= "easyShapePath";
shape.setAttribute("class", nclass);
shape.style.height = canvas.height;
shape.style.width = canvas.width;
shape.style.position = "absolute";
shape.style['z-index'] = 1;
shape.stroked = "t";
shape.strokecolor = "#000000";
if(this.properties.fill && shapetype != 'path'){
shape.filled = "t";
shape.fillcolor = this.properties.fill;
}
shape.strokeweight = ".75pt";
var xspace = parseInt(canvas.width);
xspace *=this._iemultiplier;
var yspace =parseInt(canvas.height);
yspace *= this._iemultiplier;
coordsize = xspace +"," + yspace;
shape.coordsize = coordsize;
shape.easyShape = this;
if(!appendTo){
appendTo = canvas;
}
this._cssTransform(shape,transformation,projection);
this.vml = shape;
appendTo.appendChild(shape);
}
}
,_applyProjection: function(projection,transformation){
var c = this.coords;
if(!projection) return c;
var newc = [];
for(var i=0; i < c.length-1; i+=2){
var x = parseFloat(c[i]);
var y = parseFloat(c[i+1]);
if(projection.xy){
var t = projection.xy(c[i],c[i+1],transformation);
newx= t.x;
newy= t.y;
}
cok = true;
//check we haven't wrapped around world (For flat projections sss)
if(!projection.nowrap){
var diff;
if(newx > x) diff = newx - x;
if(x > newx) diff = x - newx;
if(diff > 100) cok = false; //too extreme change
}
if(cok){
if(typeof newx == 'number' && typeof newy =='number'){
newc.push(newx);
newc.push(newy);
}
}
}
this._tcoords = newc;
this._calculateBounds(this._tcoords);
return newc;
}
,_calculateVisibleArea: function(canvas,transformation){
var left = 0,top = 0;
var right = parseInt(canvas.width) + left;
var bottom = parseInt(canvas.height) + top;
var topleft = EasyClickingUtils.undotransformation
(left,top,transformation);
var bottomright = EasyClickingUtils.undotransformation
(right,bottom,transformation);
var frame = {};
frame.top = topleft.y;
frame.bottom = bottomright.y;
frame.right = bottomright.x;
frame.left = topleft.x;
return frame;
}
,_calculatePointCoordinates: function(transformation){
if(!this.pointcoords) {
this.pointcoords = [this.coords[0],this.coords[1]];
}
var x =parseFloat(this.pointcoords[0]);
var y =parseFloat(this.pointcoords[1]);
this.setCoordinates([x,y]);
var ps = 2.5 / parseFloat(transformation.scale.x);
//should get bigger with scale increasing
var smallest = 1 / this._iemultipler;
if(ps < smallest) ps = smallest;
var newcoords =[[x-ps,y-ps],[x+ps,y-ps],[x+ps,y+ps],[x-ps, y+ps]];
var c = this._convertGeoJSONCoords(newcoords);
this.setCoordinates(c);
}
,_shapeIsInVisibleArea: function(frame){
var g = this.grid;
if(g.x2 < frame.left) {
return false;}
if(g.y2 < frame.top) {
return false;}
if(g.x1 > frame.right){
return false;
}
if(g.y1 > frame.bottom){
return false;
}
return true;
}
,_shapeIsTooSmall: function(transformation,projection){
var g = this.grid;
var s = transformation.scale;
var t1 = g.x2 -g.x1;
var t2 =g.y2- g.y1;
var delta = {x:t1,y:t2};
delta.x *= s.x;
delta.y *= s.y;
var area = delta.x * delta.y;
if(area < 40)
{return false;}//too small
else
return true;
}
/*
render the shape using canvas ctx
using ctx and a given transformation in form {translate: {x:<num>,
y:<num>}, scale:{translate: {x:<num>, y:<num>}}
projection: a function that takes xy coordinates and spits out a new
x and y
in a given viewableArea
optimisations: boolean - apply optimisations if required
*/
,render: function(canvas,transformation,projection,optimisations,
browser){
var optimisations = true;
if(!transformation){
transformation = {};
}
if(!transformation.origin)transformation.origin = {x:0,y:0};
if(!transformation.scale)transformation.scale = {x:1,y:1};
if(!transformation.translate)transformation.translate = {x:0,y:0};
var frame = this._calculateVisibleArea(canvas,transformation);
var shapetype = this.properties.shape;
if(shapetype == 'text'){
this._renderTextShape(canvas,transformation);
}
else if(shapetype == 'point'){
this._calculatePointCoordinates(transformation);
}
else if(shapetype == 'path' || shapetype =='polygon'){
}
else{
console.log("no idea how to render" +this.properties.shape+" must
be polygon|path|point");
return;
}
//optimisations = false;
if(!projection && optimisations){
if(shapetype != 'point' && shapetype != 'path' && frame){ //check
if worth drawing
if(!this._shapeIsTooSmall(transformation)) {
if(this.vml) this.vml.style.display = "none";
return;
}
if(!this._shapeIsInVisibleArea(frame)){
if(this.vml) this.vml.style.display = "none";
return;
}
}
}
if(this.vml) this.vml.style.display = '';
if(shapetype == 'text'){
//special treatment!
}
else if(!canvas.getContext) {
//this has been taken from Google ExplorerCanvas
if (!document.namespaces['g_vml_']) {
document.namespaces.add('g_vml_', 'urn:schemas-microsoft-
com:vml');
}
// Setup default CSS. Only add one style sheet per document
if (!document.styleSheets['ex_canvas_']) {
var ss = document.createStyleSheet();
ss.owningElement.id = 'ex_canvas_';
ss.cssText = 'canvas{display:inline-block;overflow:hidden;'
+
// default size is 300x150 in Gecko and Opera
'text-align:left;width:300px;height:150px}' +
'g_vml_\\:*{behavior:url(#default#VML)}';
}
this._ierender(canvas,transformation,projection,optimisations);
}
else{
this._canvasrender(canvas,transformation,projection,optimisations);
}
}
};
/*requires EasyShapes and EasyController */
var EasyMapController = function(targetjs,elem){ //elem must have
style.width and style.height
this.setMaxScaling(99999999);
if(!elem.style.position) elem.style.position = "relative";
this.wrapper = elem; //a dom element to detect mouse actions
this.targetjs = targetjs; //a js object to run actions on (with pan
and zoom functions)
var controlDiv = this.wrapper.controlDiv;
if(!controlDiv) {
controlDiv = document.createElement('div');
controlDiv.style.position = "absolute";
controlDiv.style.top = "0";
controlDiv.style.left = "0";
this.wrapper.appendChild(controlDiv);
this.wrapper.controlDiv = controlDiv;
}
this.transformation = {'translate':{x:0,y:0}, 'scale': {x:1, y:
1},'rotate': {x:0,y:0,z:0}};
//looks for specifically named function in targetjs
if(!this.targetjs.transform) alert("no transform function defined in
" + targetjs+"!");
this.wrapper.easyController = this;
};
EasyMapController.prototype = {
addMouseWheelZooming: function(){ /*not supported for internet
explorer*/
var mw = this.wrapper.onmousewheel;
var that = this;
var onmousewheel = function(e){
if (!e) /* For IE. */
e = window.event;
e.preventDefault();
/* thanks to
http://adomas.org/javascript-mouse-wheel */
var delta = 0;
var t = EasyClickingUtils.resolveTarget(e);
if(t != that.wrapper && t.parentNode !=that.wrapper) return;
if (e.wheelDelta) { /* IE/Opera. */
delta = e.wheelDelta/120;
/** In Opera 9, delta differs in sign as compared to
IE.
*/
if (window.opera)
delta = -delta;
} else if (e.detail) { /** Mozilla case. */
/** In Mozilla, sign of delta is different than in
IE.
* Also, delta is multiple of 3.
*/
delta = -e.detail/3;
}
var sensitivity = 0.3;
if(!this.lastdelta) this.lastdelta = delta;
if(delta > this.lastdelta + sensitivity || delta < this.lastdelta -
sensitivity){
var s =that.transformation.scale;
var pos = EasyClickingUtils.getMouseFromEventRelativeToCenter(e);
var t= that.transformation.translate;
var newx,newy;
if(delta > 0){
newx = parseFloat(s.x) * 2;
newy = parseFloat(s.y) * 2;
}
else{
newx = parseFloat(s.x) / 2;
newy = parseFloat(s.y) / 2;
}
if(newx > 0 && newy > 0){
s.x = newx;
s.y = newy;
that.transform();
}
}
this.lastdelta = delta;
return false;
}
var element = this.wrapper;
if (element.addEventListener){
/** DOMMouseScroll is for mozilla. */
element.addEventListener('DOMMouseScroll', onmousewheel,
false);
}
else if(element.attachEvent){
element.attachEvent("mousewheel", onmousewheel); //safari
}
else{ //it's ie.. or something non-standardised. do nowt
//window.onmousewheel = document.onmousewheel = onmousewheel;
}
},
addMousePanning: function(){
var that = this;
var md = that.wrapper.onmousedown;
var mu = that.wrapper.onmouseup;
var mm = that.wrapper.onmousemove;
var onmousemove = function(e){
if(!this.easyController)return;
var p =this.easyController.panning_status;
if(!p) return;
if(!p) return;
var t = EasyClickingUtils.resolveTarget(e);
if(t.getAttribute("class") == "easyControl") return;
var pos = EasyClickingUtils.getMouseFromEventRelativeToElement
(e,p.clickpos.x,p.clickpos.y,p.elem);
if(!pos)return;
var t = that.transformation;
//if(this.transformation) t = this.transformation;
var sc = t.scale;
var xd =parseFloat(pos.x /sc.x);
var yd = parseFloat(pos.y / sc.y);
t.translate.x = p.translate.x + xd;
t.translate.y =p.translate.y +yd;
that.transform();
if(pos.x > 5 || pos.y > 5) p.isClick = false;
if(pos.x < 5 || pos.y < 5) p.isClick = false;
return false;
};
this.wrapper.onmousedown = function(e){
if(md) md(e);
var target = EasyClickingUtils.resolveTarget(e);
if(!target) return;
if(target.getAttribute("class") == "easyControl") return;
var t = that.transformation.translate;
var sc =that.transformation.scale;
var realpos = EasyClickingUtils.getMouseFromEvent(e);
if(!realpos) return;
this.easyController = that;
var element = EasyClickingUtils.resolveTargetWithEasyClicking(e);
that.panning_status = {clickpos: realpos, translate:{x:
t.x,y:t.y},elem: element,isClick:true};
that.wrapper.onmousemove = onmousemove;
that.wrapper.style.cursor= "move";
this.style.cursor = "move";
};
this.wrapper.onmouseup = function(e){
that.wrapper.style.cursor= '';
that.wrapper.onmousemove = mm;
if(!this.easyController && mu){mu(e); return;};
if(this.easyController.panning_status &&
this.easyController.panning_status.isClick && mu){ mu(e);}
this.easyController.panning_status = null;
return false;
};
},
setTransformation: function(t){
if(!t.scale && !t.translate && !t.rotate) alert("bad transformation
applied - any call to setTransformation must contain translate,scale
and rotate");
this.transformation = t;
//console.log("transformation set",t);
//this.wrapper.transformation = t;
this.targetjs.transform(t);
//console.log("transformation set to ",t);
},
createButtonLabel: function(r,type){
var properties= {'shape':'path', stroke: '#000000',lineWidth: '1'};
var coords=[];
if(type == 'earrow'){
coords =[r,0,-r,0,'M',r,0,0,-r,"M",r,0,0,r];
}
else if(type =='warrow'){
coords =[-r,0,r,0,'M',-r,0,0,r,"M",-r,0,0,-r];
}
else if(type == 'sarrow'){
coords =[0,-r,0,r,'M',0,r,-r,0,"M",0,r,r,0];
}
else if(type == 'narrow'){
coords =[0,-r,0,r,'M',0,-r,r,0,"M",0,-r,-r,0];
}
else if(type == 'plus'){
coords =[-r,0,r,0,"M",0,-r,0,r];
}
else if(type == 'minus'){
coords = [-r,0,r,0];
}
return new EasyShape(properties,coords);
},
createButton: function(canvas,width,direction,offset,properties) {
if(!width) width = 100;
var r = width/2;
offset = {
x: offset.x || 0,
y: offset.y || 0
};
var coords = [
offset.x, offset.y,
offset.x + width, offset.y,
offset.x + width, offset.y + width,
offset.x, offset.y + width
];
properties.shape = 'polygon';
properties.fill ='rgba(150,150,150,0.7)';
var button = new EasyShape(properties,coords);
button.render(canvas,{translate:{x:0,y:0}, scale:{x:1,y:1},origin:{x:
0,y:0}});
var label = this.createButtonLabel(r,properties.buttonType);
label.render(canvas,{translate:{x:0,y:0}, scale:{x:1,y:1},origin:
{x:offset.x + r,y:offset.y + r}});
canvas.easyClicking.addToMemory(button);
return button;
},
addControl: function(controlType) {
switch(controlType) {
//case "zoom":
case "pan":
this.addPanningActions();
break;
case "zoom":
this.addZoomingActions();
break;
case "mousepanning":
this.addMousePanning();
break;
case "mousewheelzooming":
this.addMouseWheelZooming();
break;
case "rotation":
this.addRotatingActions();
break;
default:
break;
}
},
_createcontrollercanvas: function(width,height){
var newCanvas = document.createElement('canvas');
newCanvas.style.width = width;
newCanvas.style.height = height;
newCanvas.width = width;
newCanvas.height = height;
newCanvas.style.position = "absolute";
newCanvas.style.left = 0;
newCanvas.style.top = 0;
newCanvas.style["z-index"] = 3;
newCanvas.setAttribute("class","easyControl");
this.wrapper.appendChild(newCanvas);
if(!newCanvas.getContext) {
newCanvas.browser = 'ie';
}
newCanvas.easyController = this;
newCanvas.easyClicking = new EasyClicking(newCanvas);
//newCanvas.memory = [];
return newCanvas;
},
addPanningActions: function(controlDiv){
var panCanvas = this._createcontrollercanvas(44,44);
this.createButton(panCanvas,10,180,{x:16,y:2},
{'actiontype':'N','name':'pan north','buttonType': 'narrow'});
this.createButton(panCanvas,10,270,{x:30,y:16},
{'actiontype':'E','name':'pan east','buttonType': 'earrow'});
this.createButton(panCanvas,10,90,{x:16,y:16},
{'actiontype':'O','name':'re-center','buttonType': ''});
this.createButton(panCanvas,10,90,{x:2,y:16},
{'actiontype':'W','name':'pan west','buttonType': 'warrow'});
this.createButton(panCanvas,10,0,{x:16,y:30},
{'actiontype':'S','name':'pan south','buttonType': 'sarrow'});
panCanvas.onmouseup = this._panzoomClickHandler;
},
addRotatingActions: function(){
var rotateCanvas = this._createcontrollercanvas(44,40);
this.createButton(rotateCanvas,10,270,{x:30,y:16},
{'actiontype':'rotatezright','name':'rotate to right','buttonType':
'earrow'});
this.createButton(rotateCanvas,10,90,{x:2,y:16},
{'actiontype':'rotatezleft','name':'rotate to left','buttonType':
'warrow'});
rotateCanvas.onmouseup = this._panzoomClickHandler;
},
addZoomingActions: function(){
var zoomCanvas = this._createcontrollercanvas(20,30);
var left = 14;
var top = 50;
zoomCanvas.style.left = left +"px";
zoomCanvas.style.top = top + "px";
this.createButton(zoomCanvas,10,180,{x:2,y:2},
{'actiontype':'in','name':'zoom in','buttonType': 'plus'});
this.createButton(zoomCanvas,10,180,{x:2,y:16},
{'actiontype':'out','name':'zoom out','buttonType': 'minus'});
zoomCanvas.onmouseup = this._panzoomClickHandler;
},
setMaxScaling: function(max){
this._maxscale = max;
}
,transform: function(){
var t = this.transformation;
var s = t.scale;
var tr = t.translate;
var style = this.wrapper.style;
var width = parseInt(style.width);
var height = parseInt(style.height);
if(s.x <= 0) s.x = 0.1125;
if(s.y <= 0) s.y = 0.1125;
if(s.x > this._maxscale) s.x = this._maxscale;
if(s.y > this._maxscale) s.y = this._maxscale;
if(width && height){
var max = {};
max.x = parseFloat((width) - 10) * s.x;//the maximum possible
translation
max.y = parseFloat((height) - 10) * s.y;//the maximum possible
translation
if(tr.x > max.x){
tr.x = max.x;
}
else if(tr.x < -max.x){
tr.x= -max.x;
}
if(tr.y > max.y){
tr.y = max.y;
}
else if(tr.y < -max.y){
tr.y= -max.y;
}
}
//console.log(this.transformation.rotate,"rotate");
this.targetjs.transform(this.transformation);
},
_panzoomClickHandler: function(e) {
if(!e) {
e = window.event;
}
var controller = this.easyController;
var hit = this.easyClicking.getShapeAtClick(e);
if(!hit) {
return false;
}
if(!hit.properties) return false;
var pan = {};
var t =controller.transformation;
//console.log(t.rotate,"hit");
var scale =t.scale;
pan.x = parseFloat(30 / scale.x);
pan.y = parseFloat(30 / scale.y);
if(!t.scale) t.scale = {x:1,y:1};
if(!t.translate) t.translate = {x:0,y:0};
if(!t.rotate) t.rotate = {x:0,y:0,z:0};
switch(hit.properties.actiontype) {
case "W":
t.translate.x += pan.x;
break;
case "O":
t.translate.x = 0;
t.translate.y = 0;
break;
case "E":
t.translate.x -= pan.x;
break;
case "N":
t.translate.y += pan.y;
break;
case "S":
t.translate.y -= pan.y;
break;
case "in":
scale.x *= 2;
scale.y *= 2;
break;
case "out":
scale.x /= 2;
scale.y /= 2;
break;
case "rotatezright":
if(!t.rotate.z) t.rotate.z = 0;
//console.log("right",t.rotate.z);
t.rotate.z -= 0.1;
var left =6.28318531;
if(t.rotate.z <0 )t.rotate.z =left;
break;
case "rotatezleft":
if(!t.rotate.z) t.rotate.z = 0;
t.rotate.z += 0.1;
break;
default:
break;
}
controller.transform();
return false;
}
};
/*
Some common utils used throughout package
*/
if(!window.console) {
console = {
log:function(message) {
var d = document.getElementById('consolelogger');
if(d) {
d.innerHTML += message+"<<] ";
}
}
};
}
Array.prototype.contains = function(item)
{
return this.indexOf(item) != -1;
};
if(!Array.indexOf) {
Array.prototype.indexOf = function(item,from)
{
if(!from)
from = 0;
for(var i=from; i<this.length; i++) {
if(this[i] === item)
return i;
}
return -1;
};
}
var EasyMapUtils = {
googlelocalsearchurl: "
http://ajax.googleapis.com/ajax/services/
search/local?v=1.0&q="
,getLocationsFromQuery: function(query,callback){
var that = this;
var fileloadedcallback = function(status,params,responseText,url,xhr)
{
var response = eval("("+responseText+")");
if(response.responseStatus == 200){
var results = response.responseData.results;
callback(results);
return;
}
};
EasyFileUtils.loadRemoteFile(that.googlelocalsearchurl
+query,fileloadedcallback);
}
,getLongLatFromMouse: function(x,y,easyMap){
var pos = EasyClickingUtils.undotransformation
(x,y,easyMap.controller.transformation);
return {latitude:-pos.y,longitude:pos.x};
}
,_radToDeg: function(rad){
return rad / (Math.PI /180);
},
_degToRad: function(deg) {
return deg * Math.PI / 180;
},
fitgeojsontocanvas: function(json,canvas){ /*canvas must have style
width and height properties*/
var view ={};
var f =json.features;
for(var i=0; i < f.length; i++){
var c = f[i].geometry.coordinates;
for(var j=0; j < c.length; j++ ){
for(var k=0; k < c[j].length; k++){
for(var l=0; l < c[j][k].length;l++){
var x =c[j][k][l][0];
var y = c[j][k][l][1];
if(!view.x1 || x <view.x1) {
view.x1 = x;
}
else if(!view.x2 || x >view.x2) {
view.x2 = x;
}
if(!view.y1 || y <view.y1) {
view.y1 = y;
}
else if(!view.y2 || y >view.y2) {
view.y2 = y;
}
}
}
}
}
if(!json.transform) json.transform ={};
if(!json.transform.scale) json.transform.scale = {x:1, y:1};
if(!json.transform.translate) json.transform.translate = {x:0,y:0};
var canvasx = parseFloat(canvas.style.width);
var canvasy =parseFloat(canvas.style.height);
view.center = {};
view.width = (view.x2 - view.x1);
view.height = (view.y2 - view.y1)
view.center.x = view.x2 - (view.width/2);
view.center.y = view.y2 - (view.height/2);
//console.log(view.center.y, view.height);
var scale = 1,temp;
var tempx = parseFloat(canvasx/view.width);
var tempy = parseFloat(canvasy/view.height);
if(tempx < tempy) temp = tempx; else temp = tempy;
json.transform.scale.x = temp;
json.transform.scale.y = temp;
json.boundingBox = view;
json.transform.translate.x = -view.center.x;
json.transform.translate.y = view.center.y;//view.center.y;
return json;
},
/*does not yet support undoing rotating */
_testCanvas: function(ctx){
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // Mouth (clockwise)
ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // Left eye
ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // Right eye
ctx.stroke();
},
_undospherify: function (x,y,transformation){
var radius = transformation.spherical.radius;
var pos= this._spherifycoordinate(x,y,transformation);
var latitude = Math.asin(y / radius);
var longitude = Math.asin(parseFloat(x / radius) / Math.cos
(latitude));
//if(transformation.rotate.z && longitude != 'NaN')longitude -=
transformation.rotate.z;
//longitude = longitude % (6.28318531);
//if(longitude < 0) longitude = longitude
if(transformation.rotate) {
var r =transformation.rotate.z;
console.log("from",longitude);
longitude +=r;
//longitude =longitude% (6.28318531);
}
var lon = EasyMapUtils._radToDeg(longitude);
var lat = EasyMapUtils._radToDeg(latitude);
console.log("to",longitude,r,lon);
return {x:lon,y:lat};
},
_spherifycoordinate: function(lon,lat,transformation){
//
http://board.flashkit.com/board/archive/index.php/t-666832.html
var radius = transformation.spherical.radius;
var utils = EasyMapUtils;
var res = {};
var longitude = EasyMapUtils._degToRad(lon);
var latitude = EasyMapUtils._degToRad(lat);
// assume rotate values given in radians
if(transformation && transformation.rotate &&
transformation.rotate.z){
//latitude += transformation.rotate.x;
var r =parseFloat(transformation.rotate.z);
var newl =parseFloat(longitude+r);
//console.log(longitude,"-
>",newl,longitude,r,transformation.rotate.z);
longitude +=r;
}
// latitude is 90-theta, where theta is the polar angle in spherical
coordinates
// cos(90-theta) = sin(theta)
// sin(90-theta) = cos(theta)
// to transform from spherical to cartesian, we would normally use
radius*Math.cos(theta)
// hence in this case we use radius*Math.sin(latitude)
// similarly longitude is phi-180, where phi is the azimuthal angle
// cos(phi-180) = -cos(phi)
// sin(phi-180) = -sin(phi)
// to transform from spherical to cartesian, we would normally use
radius*Math.sin(theta)*Math.cos(phi)
// we must exchange for theta as above, but because of the
circular symmetry
// it does not matter whether we multiply by sin or cos of
longitude
longitude = longitude % 6.28318531; //360 degrees
res.y = (radius) * Math.sin(latitude);
if(longitude < 1.57079633 || longitude > 4.71238898){//0-90 (right)
or 270-360 (left) then on other side
res.x = (radius) * Math.cos(latitude) * Math.sin(longitude);
}
else{
//console.log(longitude,"bad",transformation.rotate.z);
res.x = false;
}
return res;
}
};
/*
EasyClicking adds the ability to associate a dom element with lots of
EasyShapes using addToMemory function
The getShapeAtClick function allows click detection on this dom
element when used in a dom mouse event handler
*/
// Depends on JQuery for offset function
// Get the current horizontal page scroll position
function findScrollX()
{
return window.scrollX || document.documentElement.scrollLeft;
}
// Get the current vertical page scroll position
function findScrollY()
{
return window.scrollY || document.documentElement.scrollTop;
}
var EasyClicking = function(element,transformation,easyShapesList){
if(element.easyClicking) {
console.log("already has easyClicking");
var update = element.easyClicking;
return update;
}
this.memory = [];
element.easyClicking = this;
if(easyShapesList) this.memory = easyShapesList;
if(transformation) this.transformation = transformation;
};
EasyClicking.prototype = {
addToMemory: function(easyShape){
if(!this.memory) this.memory = [];
easyShape._easyClickingID = this.memory.length;
this.memory.push(easyShape);
}
,clearMemory: function(){
this.memory = [];
},
getMemory: function(){
return this.memory;
}
,getMemoryID: function(easyShape){
if(easyShape && easyShape._easyClickingID)
return easyShape._easyClickingID;
else{
return false;
}
}
,getShapeAtClick: function(e){
if(!e) {
e = window.event;
}
var node = EasyClickingUtils.resolveTarget(e);
if(node.getAttribute("class") == 'easyShape') { //vml easyShape
return node.easyShape;
}
var target = EasyClickingUtils.resolveTargetWithEasyClicking(e);
if(!target) return;
var offset = $(target).offset();
x = e.clientX + window.findScrollX() - offset.left;
y = e.clientY + window.findScrollY() - offset.top;
//counter any positioning
//if(target.style.left) x -= parseInt(target.style.left);
//if(target.style.top) y -= parseInt(target.style.top);
//var memory = target.memory;
//var transformation = target.transformation;
//console.log('memory length: '+memory.length);
if(this.memory.length > 0){
var shape = target.easyClicking.getShapeAtPosition(x,y);
return shape;
} else{
//console.log("no shapes in memory");
//return false;
}
},
getShapeAtPosition: function(x,y) {
var shapes = this.memory;
if(this.transformation){
var pos = EasyClickingUtils.undotransformation
(x,y,this.transformation);
x = pos.x;
y = pos.y;
}
var hitShapes = [];
for(var i=0; i < shapes.length; i++){
var g = shapes[i].grid;
if(x >= g.x1 && x <= g.x2 && y >= g.y1 && y <=g.y2){
hitShapes.push(shapes[i]);
}
}
var res = this._findNeedleInHaystack(x,y,hitShapes);
return res;
},
_findNeedleInHaystack: function(x,y,shapes){
var hits = [];
for(var i=0; i < shapes.length; i++){
if(this._inPoly(x,y,shapes[i])) {
hits.push(shapes[i]);
}
}
if(hits.length == 0){
return null;
}
else if(hits.length == 1)
return hits[0];
else {//the click is in a polygon which is inside another polygon
var g = hits[0].grid;
var min = Math.min(g.x2 - x,x - g.x1,g.y2 - y,y - g.y1);
var closerEdge = {id:0, closeness:min};
for(var i=1; i < hits.length; i++){
g = hits[i].grid;
var min = Math.min(g.x2 - x,x - g.x1,g.y2 - y,y - g.y1);
if(closerEdge.closeness > min) {
closerEdge.id = i; closerEdge.closeness = min;
}
return hits[closerEdge.id];
}
}
},
_inPoly: function(x,y,poly) {
/* _inPoly adapted from inpoly.c
Copyright (c) 1995-1996 Galacticomm, Inc. Freeware source code.
http://www.visibone.com/inpoly/inpoly.c.txt */
var coords;
if(poly._tcoords){
coords = poly._tcoords;
//console.log("using
tcoords",x,y,poly.coords.length,poly._tcoords.length);
}
else
coords= poly.coords;
var npoints = coords.length;
if (npoints/2 < 3) {
//points don't describe a polygon
return false;
}
var inside = false;
var xold = coords[npoints-2];
var yold = coords[npoints-1];
var x1,x2,y1,y2,xnew,ynew;
for (var i=0; i<npoints; i+=2) {
xnew=coords[i];
ynew=coords[i+1];
if (xnew > xold) {
x1=xold;
x2=xnew;
y1=yold;
y2=ynew;
} else {
x1=xnew;
x2=xold;
y1=ynew;
y2=yold;
}
if ((xnew < x) == (x <= xold)
&& (y-y1)*(x2-x1) < (y2-y1)*(x-x1)) {
inside=!inside;
}
xold=xnew;
yold=ynew;
}
return inside;
}
};
var EasyClickingUtils = {
undotransformation: function(x,y,transformation){
var pos = {};
var t =transformation;
var tr =t.translate;
var s = t.scale;
var o = t.origin;
if(!x || !y)
return false;
pos.x = x;
pos.y = y;
pos.x -= o.x;
pos.y -= o.y;
pos.x /= s.x;
pos.y /= s.y;
pos.x -= tr.x;
pos.y -= tr.y;
return pos;
}
,resolveTarget:function(e)
{
if(!e) e = window.event;
var obj;
if(e.target)
obj = e.target;
else if(e.srcElement)
obj = e.srcElement;
if(obj.nodeType == 3) // defeat Safari bug
obj = obj.parentNode;
return obj;
}
,getMouseFromEvent : function(e){
if(!e) e = window.event;
var target = this.resolveTargetWithEasyClicking(e);
if(!target)return false;
var offset = $(target).offset();
if(!offset.left) return false;
x = e.clientX + window.findScrollX() - offset.left;
y = e.clientY + window.findScrollY() - offset.top;
return {'x':x, 'y':y};
}
,resolveTargetWithEasyClicking: function(e)
{
var node = EasyClickingUtils.resolveTarget(e);
var first = node;
while(node && !node.easyClicking){
node = node.parentNode;
}
if(!node) node = first;
return node;
}
,getMouseFromEventRelativeToElement: function (e,x,y,target){
if(!e) e = window.event;
var offset = $(target).offset();
if(!offset.left) return false;
oldx = e.clientX + window.findScrollX() - offset.left;
oldy = e.clientY + window.findScrollY() - offset.top;
var pos = {'x':oldx, 'y':oldy};
if(!pos) return false;
pos.x -= x;
pos.y -= y;
return pos;
}
,getMouseFromEventRelativeTo: function (e,x,y){
var pos = this.getMouseFromEvent(e);
if(!pos) return false;
pos.x -= x;
pos.y -= y;
return pos;
}
,getMouseFromEventRelativeToCenter: function(e){
var w,h;
var target = this.resolveTargetWithEasyClicking(e);
if(!target)return;
if(target.style.width)
w = parseInt(target.style.width);
else if(target.width)
w =parseInt(target.width);
if(target.style.height)
h = parseInt(target.style.height);
else if(target.height)
h = parseInt(target.height);
if(!w || !h) throw "target has no width or height (easymaputils)";
return this.getMouseFromEventRelativeTo(e,w/2,h/2);
}
};
/***
!Layer 4: Support Internet Explorer (Safely remove if not required)
***/
// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Known Issues:
//
// * Patterns are not implemented.
// * Radial gradient are not implemented. The VML version of these
look very
// different from the canvas one.
// * Clipping paths are not implemented.
// * Coordsize. The width and height attribute have higher priority
than the
// width and height style values which isn't correct.
// * Painting mode isn't implemented.
// * Canvas width/height should is using content-box by default. IE in
// Quirks mode will draw the canvas using border-box. Either change
your
// doctype to HTML5
// (
http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
// or use Box Sizing Behavior from WebFX
// (
http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
// * Non uniform scaling does not correctly scale strokes.
// * Optimize. There is always room for speed improvements.
// Only add this code if we do not already have a canvas
implementation
if (!document.createElement('canvas').getContext) {
(function() {
// alias some functions to make (compiled) code shorter
var m = Math;
var mr = m.round;
var ms = m.sin;
var mc = m.cos;
var max = m.max;
var abs = m.abs;
var sqrt = m.sqrt;
// this is used for sub pixel precision
var Z = 10;
var Z2 = Z / 2;
/**
* This funtion is assigned to the <canvas> elements as
element.getContext().
* @this {HTMLElement}
* @return {CanvasRenderingContext2D_}
*/
function getContext() {
return this.context_ ||
(this.context_ = new CanvasRenderingContext2D_(this));
}
var slice = Array.prototype.slice;
/**
* Binds a function to an object. The returned function will always
use the
* passed in {@code obj} as {@code this}.
*
* Example:
*
* g = bind(f, obj, a, b)
* g(c, d) // will do f.call(obj, a, b, c, d)
*
* @param {Function} f The function to bind the object to
* @param {Object} obj The object that should act as this when the
function
* is called
* @param {*} var_args Rest arguments that will be used as the
initial
* arguments when the function is called
* @return {Function} A new function that has bound this
*/
function bind(f, obj, var_args) {
var a = slice.call(arguments, 2);
return function() {
return f.apply(obj, a.concat(slice.call(arguments)));
};
}
var G_vmlCanvasManager_ = {
init: function(opt_doc) {
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
var doc = opt_doc || document;
// Create a dummy element so that IE will allow canvas
elements to be
// recognized.
doc.createElement('canvas');
doc.attachEvent('onreadystatechange', bind(this.init_, this,
doc));
}
},
init_: function(doc) {
// create xmlns
if (!doc.namespaces['g_vml_']) {
doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml');
}
// Setup default CSS. Only add one style sheet per document
if (!doc.styleSheets['ex_canvas_']) {
var ss = doc.createStyleSheet();
ss.owningElement.id = 'ex_canvas_';
ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
// default size is 300x150 in Gecko and Opera
'text-align:left;width:300px;height:150px}' +
'g_vml_\\:*{behavior:url(#default#VML)}';
}
// find all canvas elements
var els = doc.getElementsByTagName('canvas');
for (var i = 0; i < els.length; i++) {
this.initElement(els[i]);
}
},
/**
* Public initializes a canvas element so that it can be used as
canvas
* element from now on. This is called automatically before the
page is
* loaded but if you are creating elements using createElement you
need to
* make sure this is called on the element.
* @param {HTMLElement} el The canvas element to initialize.
* @return {HTMLElement} the element that was created.
*/
initElement: function(el) {
if (!el.getContext) {
el.getContext = getContext;
// do not use inline function because that will leak memory
el.attachEvent('onpropertychange', onPropertyChange);
el.attachEvent('onresize', onResize);
var attrs = el.attributes;
if (attrs.width && attrs.width.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setWidth_(attrs.width.nodeValue);
el.style.width = attrs.width.nodeValue + 'px';
} else {
el.width = el.clientWidth;
}
if (attrs.height && attrs.height.specified) {
// TODO: use runtimeStyle and coordsize
// el.getContext().setHeight_(attrs.height.nodeValue);
el.style.height = attrs.height.nodeValue + 'px';
} else {
el.height = el.clientHeight;
}
//el.getContext().setCoordsize_()
}
return el;
}
};
function onPropertyChange(e) {
var el = e.srcElement;
switch (e.propertyName) {
case 'width':
el.style.width = el.attributes.width.nodeValue + 'px';
el.getContext().clearRect();
break;
case 'height':
el.style.height = el.attributes.height.nodeValue + 'px';
el.getContext().clearRect();
break;
}
}
function onResize(e) {
var el = e.srcElement;
if (el.firstChild) {
el.firstChild.style.width = el.clientWidth + 'px';
el.firstChild.style.height = el.clientHeight + 'px';
}
}
G_vmlCanvasManager_.init();
// precompute "00" to "FF"
var dec2hex = [];
for (var i = 0; i < 16; i++) {
for (var j = 0; j < 16; j++) {
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
}
}
function createMatrixIdentity() {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
];
}
function matrixMultiply(m1, m2) {
var result = createMatrixIdentity();
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
var sum = 0;
for (var z = 0; z < 3; z++) {
sum += m1[x][z] * m2[z][y];
}
result[x][y] = sum;
}
}
return result;
}
function copyState(o1, o2) {
o2.fillStyle = o1.fillStyle;
o2.lineCap = o1.lineCap;
o2.lineJoin = o1.lineJoin;
o2.lineWidth = o1.lineWidth;
o2.miterLimit = o1.miterLimit;
o2.shadowBlur = o1.shadowBlur;
o2.shadowColor = o1.shadowColor;
o2.shadowOffsetX = o1.shadowOffsetX;
o2.shadowOffsetY = o1.shadowOffsetY;
o2.strokeStyle = o1.strokeStyle;
o2.globalAlpha = o1.globalAlpha;
o2.arcScaleX_ = o1.arcScaleX_;
o2.arcScaleY_ = o1.arcScaleY_;
o2.lineScale_ = o1.lineScale_;
}
function processStyle(styleString) {
var str, alpha = 1;
styleString = String(styleString);
if (styleString.substring(0, 3) == 'rgb') {
var start = styleString.indexOf('(', 3);
var end = styleString.indexOf(')', start + 1);
var guts = styleString.substring(start + 1, end).split(',');
str = '#';
for (var i = 0; i < 3; i++) {
str += dec2hex[Number(guts[i])];
}
if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
alpha = guts[3];
}
} else {
str = styleString;
}
return [str, alpha];
}
function processLineCap(lineCap) {
switch (lineCap) {
case 'butt':
return 'flat';
case 'round':
return 'round';
case 'square':
default:
return 'square';
}
}
/**
* This class implements CanvasRenderingContext2D interface as
described by
* the WHATWG.
* @param {HTMLElement} surfaceElement The element that the 2D
context should
* be associated with
*/
function CanvasRenderingContext2D_(surfaceElement) {
this.m_ = createMatrixIdentity();
this.mStack_ = [];
this.aStack_ = [];
this.currentPath_ = [];
// Canvas context properties
this.strokeStyle = '#000';
this.fillStyle = '#000';
this.lineWidth = 1;
this.lineJoin = 'miter';
this.lineCap = 'butt';
this.miterLimit = Z * 1;
this.globalAlpha = 1;
this.canvas = surfaceElement;
var el = surfaceElement.ownerDocument.createElement('div');
el.style.width = surfaceElement.clientWidth + 'px';
el.style.height = surfaceElement.clientHeight + 'px';
el.style.overflow = 'hidden';
el.style.position = 'absolute';
surfaceElement.appendChild(el);
this.element_ = el;
this.arcScaleX_ = 1;
this.arcScaleY_ = 1;
this.lineScale_ = 1;
}
var contextPrototype = CanvasRenderingContext2D_.prototype;
contextPrototype.clearRect = function() {
this.element_.innerHTML = '';
this.currentPath_ = [];
};
contextPrototype.beginPath = function() {
// TODO: Branch current matrix so that save/restore has no effect
// as per safari docs.
this.currentPath_ = [];
};
contextPrototype.moveTo = function(aX, aY) {
var p = this.getCoords_(aX, aY);
this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
this.currentX_ = p.x;
this.currentY_ = p.y;
};
contextPrototype.lineTo = function(aX, aY) {
var p = this.getCoords_(aX, aY);
this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
this.currentX_ = p.x;
this.currentY_ = p.y;
};
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY) {
var p = this.getCoords_(aX, aY);
var cp1 = this.getCoords_(aCP1x, aCP1y);
var cp2 = this.getCoords_(aCP2x, aCP2y);
bezierCurveTo(this, cp1, cp2, p);
};
// Helper function that takes the already fixed cordinates.
function bezierCurveTo(self, cp1, cp2, p) {
self.currentPath_.push({
type: 'bezierCurveTo',
cp1x: cp1.x,
cp1y: cp1.y,
cp2x: cp2.x,
cp2y: cp2.y,
x: p.x,
y: p.y
});
self.currentX_ = p.x;
self.currentY_ = p.y;
}
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
// the following is lifted almost directly from
//
http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
var cp = this.getCoords_(aCPx, aCPy);
var p = this.getCoords_(aX, aY);
var cp1 = {
x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
};
var cp2 = {
x: cp1.x + (p.x - this.currentX_) / 3.0,
y: cp1.y + (p.y - this.currentY_) / 3.0
};
bezierCurveTo(this, cp1, cp2, p);
};
contextPrototype.arc = function(aX, aY, aRadius,
aStartAngle, aEndAngle, aClockwise)
{
aRadius *= Z;
var arcType = aClockwise ? 'at' : 'wa';
var xStart = aX + mc(aStartAngle) * aRadius - Z2;
var yStart = aY + ms(aStartAngle) * aRadius - Z2;
var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
// IE won't render arches drawn counter clockwise if xStart ==
xEnd.
if (xStart == xEnd && !aClockwise) {
xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use
something
// that can be represented in binary
}
var p = this.getCoords_(aX, aY);
var pStart = this.getCoords_(xStart, yStart);
var pEnd = this.getCoords_(xEnd, yEnd);
this.currentPath_.push({type: arcType,
x: p.x,
y: p.y,
radius: aRadius,
xStart: pStart.x,
yStart: pStart.y,
xEnd: pEnd.x,
yEnd: pEnd.y});
};
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
};
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.stroke();
this.currentPath_ = [];
};
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
// Will destroy any existing path (same as FF behaviour)
this.beginPath();
this.moveTo(aX, aY);
this.lineTo(aX + aWidth, aY);
this.lineTo(aX + aWidth, aY + aHeight);
this.lineTo(aX, aY + aHeight);
this.closePath();
this.fill();
this.currentPath_ = [];
};
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1)
{
return new CanvasGradient_('gradient');
};
contextPrototype.createRadialGradient = function(aX0, aY0,
aR0, aX1,
aY1, aR1) {
var gradient = new CanvasGradient_('gradientradial');
gradient.radius1_ = aR0;
gradient.radius2_ = aR1;
gradient.focus_.x = aX0;
gradient.focus_.y = aY0;
return gradient;
};
contextPrototype.drawImage = function(image, var_args) {
var dx, dy, dw, dh, sx, sy, sw, sh;
// to find the original width we overide the width and height
var oldRuntimeWidth = image.runtimeStyle.width;
var oldRuntimeHeight = image.runtimeStyle.height;
image.runtimeStyle.width = 'auto';
image.runtimeStyle.height = 'auto';
// get the original size
var w = image.width;
var h = image.height;
// and remove overides
image.runtimeStyle.width = oldRuntimeWidth;
image.runtimeStyle.height = oldRuntimeHeight;
if (arguments.length == 3) {
dx = arguments[1];
dy = arguments[2];
sx = sy = 0;
sw = dw = w;
sh = dh = h;
} else if (arguments.length == 5) {
dx = arguments[1];
dy = arguments[2];
dw = arguments[3];
dh = arguments[4];
sx = sy = 0;
sw = w;
sh = h;
} else if (arguments.length == 9) {
sx = arguments[1];
sy = arguments[2];
sw = arguments[3];
sh = arguments[4];
dx = arguments[5];
dy = arguments[6];
dw = arguments[7];
dh = arguments[8];
} else {
throw Error('Invalid number of arguments');
}
var d = this.getCoords_(dx, dy);
var w2 = sw / 2;
var h2 = sh / 2;
var vmlStr = [];
var W = 10;
var H = 10;
// For some reason that I've now forgotten, using divs didn't work
vmlStr.push(' <g_vml_:group',
' coordsize="', Z * W, ',', Z * H, '"',
' coordorigin="0,0"' ,
' style="width:', W, ';height:', H,
';position:absolute;');
// If filters are necessary (rotation exists), create them
// filters are bog-slow, so only create them if abbsolutely
necessary
// The following check doesn't account for skews (which don't
exist
// in the canvas spec (yet) anyway.
if (this.m_[0][0] != 1 || this.m_[0][1]) {
var filter = [];
// Note the 12/21 reversal
filter.push('M11=', this.m_[0][0], ',',
'M12=', this.m_[1][0], ',',
'M21=', this.m_[0][1], ',',
'M22=', this.m_[1][1], ',',
'Dx=', mr(d.x / Z), ',',
'Dy=', mr(d.y / Z), '');
// Bounding box calculation (need to minimize displayed area so
that
// filters don't waste time on unused pixels.
var max = d;
var c2 = this.getCoords_(dx + dw, dy);
var c3 = this.getCoords_(dx, dy + dh);
var c4 = this.getCoords_(dx + dw, dy + dh);
max.x = max(max.x, c2.x, c3.x, c4.x);
max.y = max(max.y, c2.y, c3.y, c4.y);
vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
'px 0;filter:progid:DXImageTransform.Microsoft.Matrix
(',
filter.join(''), ", sizingmethod='clip');")
} else {
vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z),
'px;');
}
vmlStr.push(' ">' ,
'<g_vml_:image src="', image.src, '"',
' style="width:', Z * dw, ';',
' height:', Z * dh, ';"',
' cropleft="', sx / w, '"',
' croptop="', sy / h, '"',
' cropright="', (w - sx - sw) / w, '"',
' cropbottom="', (h - sy - sh) / h, '"',
' />',
'</g_vml_:group>');
this.element_.insertAdjacentHTML('BeforeEnd',
vmlStr.join(''));
};
contextPrototype.stroke = function(aFill) {
var lineStr = [];
var lineOpen = false;
var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
var color = a[0];
var opacity = a[1] * this.globalAlpha;
var W = 10;
var H = 10;
lineStr.push('<g_vml_:shape',
' filled="', !!aFill, '"',
' style="position:absolute;width:', W, ';height:', H,
';"',
' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H,
'"',
' stroked="', !aFill, '"',
' path="');
var newSeq = false;
var min = {x: null, y: null};
var max = {x: null, y: null};
for (var i = 0; i < this.currentPath_.length; i++) {
var p = this.currentPath_[i];
var c;
switch (p.type) {
case 'moveTo':
c = p;
lineStr.push(' m ', mr(p.x), ',', mr(p.y));
break;
case 'lineTo':
lineStr.push(' l ', mr(p.x), ',', mr(p.y));
break;
case 'close':
lineStr.push(' x ');
p = null;
break;
case 'bezierCurveTo':
lineStr.push(' c ',
mr(p.cp1x), ',', mr(p.cp1y), ',',
mr(p.cp2x), ',', mr(p.cp2y), ',',
mr(p.x), ',', mr(p.y));
break;
case 'at':
case 'wa':
lineStr.push(' ', p.type, ' ',
mr(p.x - this.arcScaleX_ * p.radius), ',',
mr(p.y - this.arcScaleY_ * p.radius), ' ',
mr(p.x + this.arcScaleX_ * p.radius), ',',
mr(p.y + this.arcScaleY_ * p.radius), ' ',
mr(p.xStart), ',', mr(p.yStart), ' ',
mr(p.xEnd), ',', mr(p.yEnd));
break;
}
// TODO: Following is broken for curves due to
// move to proper paths.
// Figure out dimensions so we can do gradient fills
// properly
if (p) {
if (min.x == null || p.x < min.x) {
min.x = p.x;
}
if (max.x == null || p.x > max.x) {
max.x = p.x;
}
if (min.y == null || p.y < min.y) {
min.y = p.y;
}
if (max.y == null || p.y > max.y) {
max.y = p.y;
}
}
}
lineStr.push(' ">');
if (!aFill) {
var lineWidth = this.lineScale_ * this.lineWidth;
// VML cannot correctly render a line if the width is less than
1px.
// In that case, we dilute the color to make the line look
thinner.
if (lineWidth < 1) {
opacity *= lineWidth;
}
lineStr.push(
'<g_vml_:stroke',
' opacity="', opacity, '"',
' joinstyle="', this.lineJoin, '"',
' miterlimit="', this.miterLimit, '"',
' endcap="', processLineCap(this.lineCap), '"',
' weight="', lineWidth, 'px"',
' color="', color, '" />'
);
} else if (typeof this.fillStyle == 'object') {
var focus = {x: '50%', y: '50%'};
var width = max.x - min.x;
var height = max.y - min.y;
var dimension = width > height ? width : height;
focus.x = mr(this.fillStyle.focus_.x / width * 100 + 50) + '%';
focus.y = mr(this.fillStyle.focus_.y / height * 100 + 50) + '%';
var colors = [];
// inside radius (%)
if (this.fillStyle.type_ == 'gradientradial') {
var inside = this.fillStyle.radius1_ / dimension * 100;
// percentage that outside radius exceeds inside radius
var expansion = this.fillStyle.radius2_ / dimension * 100 -
inside;
} else {
var inside = 0;
var expansion = 100;
}
var insidecolor = {offset: null, color: null};
var outsidecolor = {offset: null, color: null};
// We need to sort 'colors' by percentage, from 0 > 100
otherwise ie
// won't interpret it correctly
this.fillStyle.colors_.sort(function(cs1, cs2) {
return cs1.offset - cs2.offset;
});
for (var i = 0; i < this.fillStyle.colors_.length; i++) {
var fs = this.fillStyle.colors_[i];
colors.push(fs.offset * expansion + inside, '% ', fs.color,
',');
if (fs.offset > insidecolor.offset || insidecolor.offset ==
null) {
insidecolor.offset = fs.offset;
insidecolor.color = fs.color;
}
if (fs.offset < outsidecolor.offset || outsidecolor.offset ==
null) {
outsidecolor.offset = fs.offset;
outsidecolor.color = fs.color;
}
}
colors.pop();
lineStr.push('<g_vml_:fill',
' color="', outsidecolor.color, '"',
' color2="', insidecolor.color, '"',
' type="', this.fillStyle.type_, '"',
' focusposition="', focus.x, ', ', focus.y, '"',
' colors="', colors.join(''), '"',
' opacity="', opacity, '" />');
} else {
lineStr.push('<g_vml_:fill color="', color, '" opacity="',
opacity,
'" />');
}
lineStr.push('</g_vml_:shape>');
this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
};
contextPrototype.fill = function() {
this.stroke(true);
}
contextPrototype.closePath = function() {
this.currentPath_.push({type: 'close'});
};
/**
* @private
*/
contextPrototype.getCoords_ = function(aX, aY) {
var m = this.m_;
return {
x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
}
};
contextPrototype.save = function() {
var o = {};
copyState(this, o);
this.aStack_.push(o);
this.mStack_.push(this.m_);
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
};
contextPrototype.restore = function() {
copyState(this.aStack_.pop(), this);
this.m_ = this.mStack_.pop();
};
contextPrototype.translate = function(aX, aY) {
var m1 = [
[1, 0, 0],
[0, 1, 0],
[aX, aY, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.rotate = function(aRot) {
var c = mc(aRot);
var s = ms(aRot);
var m1 = [
[c, s, 0],
[-s, c, 0],
[0, 0, 1]
];
this.m_ = matrixMultiply(m1, this.m_);
};
contextPrototype.scale = function(aX, aY) {
this.arcScaleX_ *= aX;
this.arcScaleY_ *= aY;
var m1 = [
[aX, 0, 0],
[0, aY, 0],
[0, 0, 1]
];
var m = this.m_ = matrixMultiply(m1, this.m_);
// Get the line scale.
// Determinant of this.m_ means how much the area is enlarged by
the
// transformation. So its square root can be used as a scale
factor
// for width.
var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
this.lineScale_ = sqrt(abs(det));
};
/******** STUBS ********/
contextPrototype.clip = function() {
// TODO: Implement
};
contextPrototype.arcTo = function() {
// TODO: Implement
};
contextPrototype.createPattern = function() {
return new CanvasPattern_;
};
// Gradient / Pattern Stubs
function CanvasGradient_(aType) {
this.type_ = aType;
this.radius1_ = 0;
this.radius2_ = 0;
this.colors_ = [];
this.focus_ = {x: 0, y: 0};
}
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
aColor = processStyle(aColor);
this.colors_.push({offset: 1 - aOffset, color: aColor});
};
function CanvasPattern_() {}
// set up externs
G_vmlCanvasManager = G_vmlCanvasManager_;
CanvasRenderingContext2D = CanvasRenderingContext2D_;
CanvasGradient = CanvasGradient_;
CanvasPattern = CanvasPattern_;
})();
} // if
/*
* jQuery 1.2.6 - New Wave Javascript
*
* Copyright (c) 2008 John Resig (
jquery.com)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
* $Rev: 5685 $
*/
(function(){var _jQuery=window.jQuery,_$=window.$;var
jQuery=window.jQuery=window.$=function(selector,context){return new
jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]
*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*
$/,undefined;jQuery.fn=jQuery.prototype={init:function
(selector,context){selector=selector||document;if(selector.nodeType)
{this[0]=selector;this.length=1;return this;}if(typeof
selector=="string"){var match=quickExpr.exec(selector);if(match&&(match
[1]||!context)){if(match[1])selector=jQuery.clean([match
[1]],context);else{var elem=document.getElementById(match[3]);if(elem)
{if(
elem.id!=match[3])return jQuery().find(selector);return jQuery
(elem);}selector=[];}}else
return jQuery(context).find(selector);}else if(jQuery.isFunction
(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"]
(selector);return this.setArray(jQuery.makeArray
(selector));},jquery:"1.2.6",size:function(){return
this.length;},length:0,get:function(num){return num==undefined?
jQuery.makeArray(this):this[num];},pushStack:function(elems){var
ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function
(elems){this.length=0;Array.prototype.push.apply(this,elems);return
this;},each:function(callback,args){return jQuery.each
(this,callback,args);},index:function(elem){var ret=-1;return
jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function
(name,value,type){var options=name;if(name.constructor==String)if
(value===undefined)return this[0]&&jQuery[type||"attr"](this
[0],name);else{options={};options[name]=value;}return this.each
(function(i){for(name in options)jQuery.attr(type?
this.style:this,name,jQuery.prop(this,options
[name],type,i,name));});},css:function(key,value){if((key=='width'||
key=='height')&&parseFloat(value)<0)value=undefined;return this.attr
(key,value,"curCSS");},text:function(text){if(typeof text!
="object"&&text!=null)return this.empty().append((this[0]&&this
[0].ownerDocument||document).createTextNode(text));var
ret="";jQuery.each(text||this,function(){jQuery.each
(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?
this.nodeValue:jQuery.fn.text([this]);});});return
ret;},wrapAll:function(html){if(this[0])jQuery(html,this
[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var
elem=this;while(elem.firstChild)elem=elem.firstChild;return
elem;}).append(this);return this;},wrapInner:function(html){return
this.each(function(){jQuery(this).contents().wrapAll
(html);});},wrap:function(html){return this.each(function(){jQuery
(this).wrapAll(html);});},append:function(){return this.domManip
(arguments,true,false,function(elem){if(this.nodeType==1)
this.appendChild(elem);});},prepend:function(){return this.domManip
(arguments,true,true,function(elem){if(this.nodeType==1)
this.insertBefore(elem,this.firstChild);});},before:function(){return
this.domManip(arguments,false,false,function(elem)
{this.parentNode.insertBefore(elem,this);});},after:function(){return
this.domManip(arguments,false,true,function(elem)
{this.parentNode.insertBefore(elem,this.nextSibling);});},end:function
(){return this.prevObject||jQuery([]);},find:function(selector){var
elems=jQuery.map(this,function(elem){return jQuery.find
(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||
selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function
(events){var ret=this.map(function(){if(jQuery.browser.msie&&!
jQuery.isXMLDoc(this)){var clone=this.cloneNode
(true),container=document.createElement("div");container.appendChild
(clone);return jQuery.clean([container.innerHTML])[0];}else
return this.cloneNode(true);});var clone=ret.find("*").andSelf().each
(function(){if(this[expando]!=undefined)this[expando]=null;});if
(events===true)this.find("*").andSelf().each(function(i){if
(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var
type in events)for(var handler in events[type])jQuery.event.add(clone
[i],type,events[type][handler],events[type][handler].data);});return
ret;},filter:function(selector){return this.pushStack(jQuery.isFunction
(selector)&&jQuery.grep(this,function(elem,i){return selector.call
(elem,i);})||jQuery.multiFilter(selector,this));},not:function
(selector){if(selector.constructor==String)if(isSimple.test(selector))
return this.pushStack(jQuery.multiFilter(selector,this,true));else
selector=jQuery.multiFilter(selector,this);var
isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!
selector.nodeType;return this.filter(function(){return isArrayLike?
jQuery.inArray(this,selector)<0:this!=selector;});},add:function
(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get
(),typeof selector=='string'?jQuery(selector):jQuery.makeArray
(selector))));},is:function(selector){return!!
selector&&jQuery.multiFilter
(selector,this).length>0;},hasClass:function(selector){return
this.is
("."+selector);},val:function(value){if(value==undefined){if
(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var
index=elem.selectedIndex,values=
[],options=elem.options,one=elem.type=="select-one";if(index<0)return
null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++)
{var option=options[i];if(option.selected){value=jQuery.browser.msie&&!
option.attributes.value.specified?option.text:option.value;if(one)
return value;values.push(value);}}return values;}else
return(this[0].value||"").replace(/\r/g,"");}return undefined;}if
(value.constructor==Number)value+='';return this.each(function(){if
(this.nodeType!=1)return;if(value.constructor==Array&&/radio|
checkbox/.test(this.type))this.checked=(jQuery.inArray
(this.value,value)>=0||jQuery.inArray(
this.name,value)>=0);else if
(jQuery.nodeName(this,"select")){var values=jQuery.makeArray
(value);jQuery("option",this).each(function(){this.selected=
(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)
>=0);});if(!values.length)this.selectedIndex=-1;}else
this.value=value;});},html:function(value){return value==undefined?
(this[0]?this[0].innerHTML:null):this.empty().append
(value);},replaceWith:function(value){return this.after(value).remove
();},eq:function(i){return this.slice(i,i+1);},slice:function(){return
this.pushStack(Array.prototype.slice.apply
(this,arguments));},map:function(callback){return this.pushStack
(jQuery.map(this,function(elem,i){return callback.call
(elem,i,elem);}));},andSelf:function(){return this.add
(this.prevObject);},data:function(key,value){var parts=key.split
(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var
data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if
(data===undefined&&this.length)data=jQuery.data(this[0],key);return
data===undefined&&parts[1]?this.data(parts[0]):data;}else
return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each
(function(){jQuery.data(this,key,value);});},removeData:function(key)
{return this.each(function(){jQuery.removeData
(this,key);});},domManip:function(args,table,reverse,callback){var
clone=this.length>1,elems;return this.each(function(){if(!elems)
{elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse
();}var obj=this;if(table&&jQuery.nodeName(this,"table")
&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")
[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var
scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery
(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))
scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add
(jQuery("script",elem).remove());callback.call
(obj,elem);}});scripts.each
(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function
evalScript(i,elem){if(elem.src)jQuery.ajax
({url:elem.src,async:false,dataType:"script"});else
jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if
(elem.parentNode)elem.parentNode.removeChild(elem);}function now()
{return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var
target=arguments[0]||
{},i=1,length=arguments.length,deep=false,options;if
(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}
if(typeof target!="object"&&typeof target!="function")target={};if
(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments
[i])!=null)for(var name in options){var src=target[name],copy=options
[name];if(target===copy)continue;if(deep&©&&typeof
copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||
(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]
=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData=
{},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/
i,defaultView=document.defaultView||{};jQuery.extend
({noConflict:function(deep){window.$=_$;if(deep)
window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!
fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?
function/.test(fn+"");},isXMLDoc:function(elem){return
elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!
elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim
(data);if(data){var head=document.getElementsByTagName("head")[0]||
document.documentElement,script=document.createElement
("script");script.type="text/javascript";if(jQuery.browser.msie)
script.text=data;else
script.appendChild(document.createTextNode(data));head.insertBefore
(script,head.firstChild);head.removeChild(script);}},nodeName:function
(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()
==name.toUpperCase();},cache:{},data:function(elem,name,data)
{elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem
[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if
(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache
[id][name]:id;},removeData:function(elem,name){elem=elem==window?
windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id])
{delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])
break;if(!name)jQuery.removeData(elem);}}else{try{delete elem
[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute
(expando);}delete jQuery.cache[id];}},each:function
(object,callback,args){var name,i=0,length=object.length;if(args){if
(length==undefined){for(name in object)if(callback.apply(object
[name],args)===false)break;}else
for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else
{if(length==undefined){for(name in object)if(callback.call(object
[name],name,object[name])===false)break;}else
for(var value=object[0];i<length&&callback.call(value,i,value)!
==false;value=object[++i]){}}return object;},prop:function
(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call
(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!
exclude.test(name)?value+"px":value;},className:{add:function
(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function
(i,className){if(elem.nodeType==1&&!jQuery.className.has
(elem.className,className))elem.className+=(elem.className?" ":"")
+className;});},remove:function(elem,classNames){if(elem.nodeType==1)
elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/
\s+/),function(className){return!jQuery.className.has
(classNames,className);}).join(" "):"";},has:function(elem,className)
{return jQuery.inArray(className,(elem.className||elem).toString
().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old=
{};for(var name in options){old[name]=elem.style[name];elem.style[name]
=options[name];}callback.call(elem);for(var name in options)elem.style
[name]=old[name];},css:function(elem,name,force){if(name=="width"||
name=="height"){var val,props=
{position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?
["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?
elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each
(which,function(){padding+=parseFloat(jQuery.curCSS
(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS
(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding
+border);}if(jQuery(elem).is(":visible"))getWH();else
jQuery.swap(elem,props,getWH);return Math.max(0,val);}return
jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var
ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)
return false;var ret=defaultView.getComputedStyle(elem,null);return!
ret||ret.getPropertyValue("color")=="";}if
(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr
(style,"opacity");return ret==""?"1":ret;}if
(jQuery.browser.opera&&name=="display"){var
save=style.outline;style.outline="0 solid black";style.outline=save;}if
(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])
ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/
float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase
();var computedStyle=defaultView.getComputedStyle(elem,null);if
(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue
(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color
(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color
(stack[i])){swap[i]=stack[i].style.display;stack
[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!
=null?"none":(computedStyle&&computedStyle.getPropertyValue
(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack
[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if
(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function
(all,letter){return letter.toUpperCase();});ret=elem.currentStyle
[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^
\d/.test(ret)){var
left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||
0;ret=style.pixelLeft
+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return
ret;},clean:function(elems,context){var ret=[];context=context||
document;if(typeof context.createElement=='undefined')
context=context.ownerDocument||context[0]&&context[0].ownerDocument||
document;jQuery.each(elems,function(i,elem){if(!elem)return;if
(elem.constructor==Number)elem+='';if(typeof elem=="string")
{elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return
tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?
all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase
(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&
[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&
[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|
cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&
[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!
tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></
table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></
tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&
[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap
[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var
tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?
div.firstChild&&div.firstChild.childNodes:wrap[1]
=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var
j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody
[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^
\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^
\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if
(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName
(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName
(elem,"form")||elem.options)ret.push(elem);else
ret=jQuery.merge(ret,elem);});return ret;},attr:function
(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return
undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!
==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||
name;if(elem.tagName){var special=/href|src|style/.test(name);if
(name=="selected"&&jQuery.browser.safari)
elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if
(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)
throw"type property can't be changed";elem[name]=value;}if
(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return
elem.getAttributeNode(name).nodeValue;return elem[name];}if
(msie&¬xml&&name=="style")return jQuery.attr
(elem.style,"cssText",value);if(set)elem.setAttribute
(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,
2):elem.getAttribute(name);return attr===null?undefined:attr;}if
(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=
(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)
+''=="NaN"?"":"alpha(opacity="+value*100+")");}return
elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat
(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace
(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if
(set)elem[name]=value;return elem[name];},trim:function(text){return
(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var
ret=[];if(array!=null){var i=array.length;if(i==null||array.split||
array.setInterval||array.call)ret[0]=array;else
while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array)
{for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)
return i;return-1;},merge:function(first,second){var
i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i+
+])if(elem.nodeType!=8)first[pos++]=elem;}else
while(elem=second[i++])first[pos++]=elem;return first;},unique:function
(array){var ret=[],done={};try{for(var
i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!
done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}
return ret;},grep:function(elems,callback,inv){var ret=[];for(var
i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))
ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=
[];for(var i=0,length=elems.length;i<length;i++){var value=callback
(elems[i],i);if(value!=null)ret[ret.length]=value;}return
ret.concat.apply([],ret);}});var
userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:
(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/
webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test
(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)
&&!/(compatible|webkit)/.test(userAgent)};var
styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend
({boxModel:!jQuery.browser.msie||
document.compatMode=="CSS1Compat",props:
{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each
({parent:function(elem){return elem.parentNode;},parents:function(elem)
{return jQuery.dir(elem,"parentNode");},next:function(elem){return
jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return
jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return
jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return
jQuery.dir(elem,"previousSibling");},siblings:function(elem){return
jQuery.sibling(elem.parentNode.firstChild,elem);},children:function
(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem)
{return jQuery.nodeName(elem,"iframe")?elem.contentDocument||
elem.contentWindow.document:jQuery.makeArray
(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function
(selector){var ret=jQuery.map(this,fn);if(selector&&typeof
selector=="string")ret=jQuery.multiFilter(selector,ret);return
this.pushStack(jQuery.unique(ret));};});jQuery.each
({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function
(name,original){jQuery.fn[name]=function(){var args=arguments;return
this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery
(args[i])[original](this);});};});jQuery.each({removeAttr:function
(name){jQuery.attr(this,name,"");if(this.nodeType==1)
this.removeAttribute(name);},addClass:function(classNames)
{jQuery.className.add(this,classNames);},removeClass:function
(classNames){jQuery.className.remove
(this,classNames);},toggleClass:function(classNames){jQuery.className
[jQuery.className.has(this,classNames)?"remove":"add"]
(this,classNames);},remove:function(selector){if(!selector||
jQuery.filter(selector,[this]).r.length){jQuery("*",this).add
(this).each(function(){jQuery.event.remove(this);jQuery.removeData
(this);});if(this.parentNode)this.parentNode.removeChild
(this);}},empty:function(){jQuery(">*",this).remove();while
(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn)
{jQuery.fn[name]=function(){return this.each
(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name)
{var type=name.toLowerCase();jQuery.fn[type]=function(size){return this
[0]==window?jQuery.browser.opera&&document.body["client"+name]||
jQuery.browser.safari&&window["inner"+name]||
document.compatMode=="CSS1Compat"&&document.documentElement
["client"+name]||document.body["client"+name]:this[0]==document?
Math.max(Math.max(document.body["scroll"+name],document.documentElement
["scroll"+name]),Math.max(document.body
["offset"+name],document.documentElement
["offset"+name])):size==undefined?(this.length?jQuery.css(this
[0],type):null):this.css(type,size.constructor==String?size:size
+"px");};});function num(elem,prop){return elem[0]&&parseInt
(jQuery.curCSS(elem[0],prop,true),10)||0;}var
chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:
[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new
RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)
("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars
+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||
jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute
("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function
(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m
[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i)
{return i==0;},last:function(a,i,m,r){return
i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i)
{return i%2;},"first-child":function(a){return
a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a)
{return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")
==a;},"only-child":function(a){return!jQuery.nth
(a.parentNode.lastChild,2,"previousSibling");},parent:function(a)
{return a.firstChild;},empty:function(a){return!
a.firstChild;},contains:function(a,i,m){return(a.textContent||
a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function
(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css
(a,"visibility")!="hidden";},hidden:function(a)
{return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css
(a,"visibility")=="hidden";},enabled:function(a){return!
a.disabled;},disabled:function(a){return a.disabled;},checked:function
(a){return a.checked;},selected:function(a){return a.selected||
jQuery.attr(a,"selected");},text:function(a)
{return"text"==a.type;},radio:function(a)
{return"radio"==a.type;},checkbox:function(a)
{return"checkbox"==a.type;},file:function(a)
{return"file"==a.type;},password:function(a)
{return"password"==a.type;},submit:function(a)
{return"submit"==a.type;},image:function(a)
{return"image"==a.type;},reset:function(a)
{return"reset"==a.type;},button:function(a){return"button"==a.type||
jQuery.nodeName(a,"button");},input:function(a){return/input|select|
textarea|button/i.test(a.nodeName);},has:function(a,i,m){return
jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test
(a.nodeName);},animated:function(a){return jQuery.grep
(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\
[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?
(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars
+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while
(expr&&expr!=old){old=expr;var f=jQuery.filter
(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?
elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context)
{if(typeof t!="string")return[t];if(context&&context.nodeType!
=1&&context.nodeType!=9)return[];context=context||document;var ret=
[context],done=[],last,nodeName;while(t&&last!=t){var r=
[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec
(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var
c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&
(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push
(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)
continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec
(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for
(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret
[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if
(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!
nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]
=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace
(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if
(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=
[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var
m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else
{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret
[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!
jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if
((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof
oid.id=="string"&&
oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)
[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for
(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]
==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")
tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]
==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var
i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}
r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter
(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if
(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return
done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var
i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!
not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function
(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for
(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]
=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")
r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m
[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]
=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r
[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test
(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m
[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||
type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||
type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]
==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\
+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m
[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test
[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r
[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge
[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if
(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if
(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-
last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)
tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof
fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||
function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i)
{return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function
(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if
(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return
matched;},nth:function(cur,result,dir,elem){result=result||1;var
num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)
break;return cur;},sibling:function(n,elem){var r=[];for
(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return
r;}});jQuery.event={add:function(elem,types,handler,data){if
(elem.nodeType==3||elem.nodeType==8)return;if
(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)
handler.guid=this.guid++;if(data!=undefined){var
fn=handler;handler=this.proxy(fn,function(){return fn.apply
(this,arguments);});handler.data=data;}var events=jQuery.data
(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data
(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!
="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply
(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each
(types.split(/\s+/),function(index,type){var parts=type.split
(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if
(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||
jQuery.event.special[type].setup.call(elem)===false){if
(elem.addEventListener)elem.addEventListener(type,handle,false);else if
(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers
[handler.guid]=handler;jQuery.event.global[type]
=true;});elem=null;},guid:1,global:{},remove:function
(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var
events=jQuery.data(elem,"events"),ret,index;if(events){if
(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for
(var type in events)this.remove(elem,type+(types||""));else{if
(types.type){handler=types.handler;types=types.type;}jQuery.each
(types.split(/\s+/),function(index,type){var parts=type.split
(".");type=parts[0];if(events[type]){if(handler)delete events[type]
[handler.guid];else
for(handler in events[type])if(!parts[1]||events[type]
[handler].type==parts[1])delete events[type][handler];for(ret in events
[type])break;if(!ret){if(!jQuery.event.special[type]||
jQuery.event.special[type].teardown.call(elem)===false){if
(elem.removeEventListener)elem.removeEventListener(type,jQuery.data
(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent
("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events
[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data
(elem,"handle");if(handle)handle.elem=null;jQuery.removeData
(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function
(type,data,elem,donative,extra){data=jQuery.makeArray(data);if
(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!
elem){if(this.global[type])jQuery("*").add([window,document]).trigger
(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return
undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!
data[0]||!data[0].preventDefault;if(event){data.unshift
({type:type,target:elem,preventDefault:function()
{},stopPropagation:function(){},timeStamp:now()});data[0][expando]
=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var
handle=jQuery.data(elem,"handle");if(handle)val=handle.apply
(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem
["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if
(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply
(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if
(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')
&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}
this.triggered=false;}return val;},handle:function(event){var
val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix
(event||window.event);namespace=event.type.split
(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!
event.exclusive;handlers=(jQuery.data(this,"events")||{})
[event.type];for(var j in handlers){var handler=handlers[j];if(all||
handler.type==namespace)
{event.handler=handler;event.data=handler.data;ret=handler.apply
(this,arguments);if(val!==false)val=ret;if(ret===false)
{event.preventDefault();event.stopPropagation();}}}return
val;},fix:function(event){if(event[expando]==true)return event;var
originalEvent=event;event={originalEvent:originalEvent};var
props="altKey attrChange attrName bubbles button cancelable charCode
clientX clientY ctrlKey currentTarget data detail eventPhase
fromElement handler keyCode metaKey newValue originalTarget pageX
pageY prevValue relatedNode relatedTarget screenX screenY shiftKey
srcElement target timeStamp toElement type view wheelDelta which".split
(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props
[i]];event[expando]=true;event.preventDefault=function(){if
(originalEvent.preventDefault)originalEvent.preventDefault
();originalEvent.returnValue=false;};event.stopPropagation=function()
{if(originalEvent.stopPropagation)originalEvent.stopPropagation
();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||
now();if(!event.target)event.target=event.srcElement||document;if
(event.target.nodeType==3)event.target=event.target.parentNode;if(!
event.relatedTarget&&event.fromElement)
event.relatedTarget=event.fromElement==event.target?
event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!
=null){var
doc=document.documentElement,body=document.body;event.pageX=event.clientX
+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||
0);event.pageY=event.clientY+(doc&&doc.scrollTop||
body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&
((event.charCode||event.charCode===0)?event.charCode:event.keyCode))
event.which=event.charCode||event.keyCode;if(!
event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!
event.which&&event.button)event.which=(event.button&1?1:
(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function
(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return
proxy;},special:{ready:{setup:function(){bindReady
();return;},teardown:function(){return;}},mouseenter:{setup:function()
{if(jQuery.browser.msie)return false;jQuery(this).bind
("mouseover",jQuery.event.special.mouseenter.handler);return
true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery
(this).unbind
("mouseover",jQuery.event.special.mouseenter.handler);return
true;},handler:function(event){if(withinElement(event,this))return
true;event.type="mouseenter";return jQuery.event.handle.apply
(this,arguments);}},mouseleave:{setup:function(){if
(jQuery.browser.msie)return false;jQuery(this).bind
("mouseout",jQuery.event.special.mouseleave.handler);return
true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery
(this).unbind
("mouseout",jQuery.event.special.mouseleave.handler);return
true;},handler:function(event){if(withinElement(event,this))return
true;event.type="mouseleave";return jQuery.event.handle.apply
(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn)
{return type=="unload"?this.one(type,data,fn):this.each(function()
{jQuery.event.add(this,type,fn||data,fn&&data);});},one:function
(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event)
{jQuery(this).unbind(event,one);return(fn||data).apply
(this,arguments);});return this.each(function(){jQuery.event.add
(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each
(function(){jQuery.event.remove(this,type,fn);});},trigger:function
(type,data,fn){return this.each(function(){jQuery.event.trigger
(type,data,this,true,fn);});},triggerHandler:function(type,data,fn)
{return this[0]&&jQuery.event.trigger(type,data,this
[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while
(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click
(jQuery.event.proxy(fn,function(event){this.lastToggle=
(this.lastToggle||0)%i;event.preventDefault();return args
[this.lastToggle++].apply(this,arguments)||false;}));},hover:function
(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind
('mouseleave',fnOut);},ready:function(fn){bindReady();if
(jQuery.isReady)fn.call(document,jQuery);else
jQuery.readyList.push(function(){return fn.call(this,jQuery);});return
this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if
(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each
(jQuery.readyList,function(){this.call
(document);});jQuery.readyList=null;}jQuery(document).triggerHandler
("ready");}}});var readyBound=false;function bindReady(){if(readyBound)
return;readyBound=true;if(document.addEventListener&&!
jQuery.browser.opera)document.addEventListener
("DOMContentLoaded",jQuery.ready,false);if
(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)
return;try{document.documentElement.doScroll("left");}catch(error)
{setTimeout(arguments.callee,0);return;}jQuery.ready();})();if
(jQuery.browser.opera)document.addEventListener
("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var
i=0;i<document.styleSheets.length;i++)if(document.styleSheets
[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready
();},false);if(jQuery.browser.safari){var numStyles;(function(){if
(jQuery.isReady)return;if(document.readyState!
="loaded"&&document.readyState!="complete"){setTimeout
(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery
("style, link[rel=stylesheet]").length;if(document.styleSheets.length!
=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})
();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each
(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split
(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind
(name,fn):this.trigger(name);};});var withinElement=function
(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)
try{parent=parent.parentNode;}catch(error){parent=elem;}return
parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add
(document).unbind();});jQuery.fn.extend
({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof
url!='string')return this._load(url);var off=url.indexOf(" ");if
(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}
callback=callback||function(){};var type="GET";if(params)if
(jQuery.isFunction(params)){callback=params;params=null;}else
{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax
({url:url,type:type,dataType:"html",data:params,complete:function
(res,status){if(status=="success"||status=="notmodified")self.html
(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|
\s)*?\/script>/g,"")).find(selector):res.responseText);self.each
(callback,[res.responseText,status,res]);}});return
this;},serialize:function(){return jQuery.param(this.serializeArray
());},serializeArray:function(){return this.map(function(){return
jQuery.nodeName(this,"form")?jQuery.makeArray
(this.elements):this;}).filter(function(){return
this.name&&!
this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/
text|hidden|password/i.test(this.type));}).map(function(i,elem){var
val=jQuery(this).val();return val==null?null:val.constructor==Array?
jQuery.map(val,function(val,i){return{name:
elem.name,value:val};}):
{name:
elem.name,value:val};}).get();}});jQuery.each
("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split
(","),function(i,o){jQuery.fn[o]=function(f){return this.bind
(o,f);};});var jsc=now();jQuery.extend({get:function
(url,data,callback,type){if(jQuery.isFunction(data))
{callback=data;data=null;}return jQuery.ajax
({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function
(url,callback){return jQuery.get
(url,null,callback,"script");},getJSON:function(url,data,callback)
{return jQuery.get(url,data,callback,"json");},post:function
(url,data,callback,type){if(jQuery.isFunction(data))
{callback=data;data={};}return jQuery.ajax
({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function
(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:
{url:location.href,global:true,type:"GET",timeout:
0,contentType:"application/x-www-form-
urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:
{xml:"application/xml, text/xml",html:"text/html",script:"text/
javascript, application/javascript",json:"application/json, text/
javascript",text:"text/plain",_default:"*/*"}},lastModified:
{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,
{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/
g,status,data,type=s.type.toUpperCase();if
(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param
(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))
s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if
(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+
(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&
(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if
(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp
+"$1");s.url=s.url.replace(jsre,"="+jsonp
+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success
();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch
(e){}if(head)head.removeChild(script);};}if
(s.dataType=="script"&&s.cache==null)s.cache=false;if
(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|
&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/
\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match
(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)
jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]
+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)
&&remote.exec(s.url)[1]!=location.host){var
head=document.getElementsByTagName("head")[0];var
script=document.createElement("script");script.src=s.url;if
(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var
done=false;script.onload=script.onreadystatechange=function(){if(!
done&&(!this.readyState||this.readyState=="loaded"||
this.readyState=="complete")){done=true;success();complete
();head.removeChild(script);}};}head.appendChild(script);return
undefined;}var requestDone=false;var xhr=window.ActiveXObject?new
ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)
xhr.open(type,s.url,s.async,s.username,s.password);else
xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader
("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader
("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970
00:00:00 GMT");xhr.setRequestHeader("X-Requested-
With","XMLHttpRequest");xhr.setRequestHeader
("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */
*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)
===false){s.global&&jQuery.active--;xhr.abort();return false;}if
(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var
onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&
(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival)
{clearInterval(ival);ival=null;}
status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)
&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)
&&"notmodified"||"success";if(status=="success"){try
{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e)
{status="parsererror";}}if(status=="success"){var modRes;try
{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if
(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)
success();}else
jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if
(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)
setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)
onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}
catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)
onreadystatechange();function success(){if(s.success)s.success
(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}
function complete(){if(s.complete)s.complete(xhr,status);if(s.global)
jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--
jQuery.active)jQuery.event.trigger("ajaxStop");}return
xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error
(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",
[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!
xhr.status&&location.protocol=="file:"||
(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||
jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return
false;},httpNotModified:function(xhr,url){try{var
xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||
xhrRes==jQuery.lastModified[url]||
jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return
false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader
("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")
>=0,data=xml?xhr.responseXML:xhr.responseText;if
(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if
(filter)data=filter(data,type);if(type=="script")jQuery.globalEval
(data);if(type=="json")data=eval("("+data+")");return
data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)
jQuery.each(a,function(){s.push(encodeURIComponent(
this.name)
+"="+encodeURIComponent(this.value));});else
for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a
[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent
(this));});else
s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a
[j])?a[j]():a[j]));return s.join("&").replace(/%20/
g,"+");}});jQuery.fn.extend({show:function(speed,callback){return
speed?this.animate
({height:"show",width:"show",opacity:"show"},speed,callback):this.filter
(":hidden").each(function(){this.style.display=this.oldblock||"";if
(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName
+" />").appendTo("body");this.style.display=elem.css("display");if
(this.style.display=="none")this.style.display="block";elem.remove
();}}).end();},hide:function(speed,callback){return speed?this.animate
({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter
(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css
(this,"display");this.style.display="none";}).end
();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return
jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply
(this,arguments):fn?this.animate
({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each
(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]
();});},slideDown:function(speed,callback){return this.animate
({height:"show"},speed,callback);},slideUp:function(speed,callback)
{return this.animate
({height:"hide"},speed,callback);},slideToggle:function(speed,callback)
{return this.animate
({height:"toggle"},speed,callback);},fadeIn:function(speed,callback)
{return this.animate
({opacity:"show"},speed,callback);},fadeOut:function(speed,callback)
{return this.animate({opacity:"hide"},speed,callback);},fadeTo:function
(speed,to,callback){return this.animate
({opacity:to},speed,callback);},animate:function
(prop,speed,easing,callback){var optall=jQuery.speed
(speed,easing,callback);return this
[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)
return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is
(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop
[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||
p=="width"){opt.display=jQuery.css
(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!
=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend
({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx
(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?
hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^
([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var
end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style
[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style
[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)
+start;e.custom(start,end,unit);}else
e.custom(start,val,"");}});return true;});},queue:function(type,fn){if
(jQuery.isFunction(type)||(type&&type.constructor==Array))
{fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue
(this[0],type);return this.each(function(){if(fn.constructor==Array)
queue(this,type,fn);else{queue(this,type).push(fn);if(queue
(this,type).length==1)fn.call(this);}});},stop:function
(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue
([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers
[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!
gotoEnd)this.dequeue();return this;}});var queue=function
(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type
+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray
(array));}return q;};jQuery.fn.dequeue=function(type)
{type=type||"fx";return this.each(function(){var q=queue
(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend
({speed:function(speed,easing,fn){var
opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||
jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||
easing&&easing.constructor!=Function&&easing};opt.duration=
(opt.duration&&opt.duration.constructor==Number?
opt.duration:jQuery.fx.speeds[opt.duration])||
jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if
(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction
(opt.old))opt.old.call(this);};return opt;},easing:{linear:function
(p,n,firstNum,diff){return firstNum+diff*p;},swing:function
(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff
+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop)
{this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)
options.orig={};}});jQuery.fx.prototype={update:function(){if
(this.options.step)this.options.step.call(this.elem,this.now,this);
(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if
(this.prop=="height"||this.prop=="width")
this.elem.style.display="block";},cur:function(force){if(this.elem
[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem
[this.prop];var r=parseFloat(jQuery.css
(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat
(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit)
{this.startTime=now();this.start=from;this.end=to;this.unit=unit||
this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update
();var self=this;function t(gotoEnd){return self.step(gotoEnd);}
t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null)
{jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for
(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!
timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},
13);}},show:function(){this.options.orig[this.prop]=jQuery.attr
(this.elem.style,this.prop);this.options.show=true;this.custom
(0,this.cur());if(this.prop=="width"||this.prop=="height")
this.elem.style[this.prop]="1px";jQuery(this.elem).show
();},hide:function(){this.options.orig[this.prop]=jQuery.attr
(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur
(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||
t>this.options.duration+this.startTime)
{this.now=this.end;this.pos=this.state=1;this.update
();this.options.curAnim[this.prop]=true;var done=true;for(var i in
this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if
(done){if(this.options.display!=null)
{this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if
(jQuery.css(this.elem,"display")=="none")
this.elem.style.display="block";}if(this.options.hide)
this.elem.style.display="none";if(this.options.hide||this.options.show)
for(var p in this.options.curAnim)jQuery.attr
(this.elem.style,p,this.options.orig[p]);}if(done)
this.options.complete.call(this.elem);return false;}else{var n=t-
this.startTime;this.state=n/
this.options.duration;this.pos=jQuery.easing[this.options.easing||
(jQuery.easing.swing?"swing":"linear")](this.state,n,
0,1,this.options.duration);this.now=this.start+((this.end-this.start)
*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,
{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx)
{fx.elem.scrollLeft=fx.now;},scrollTop:function(fx)
{fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr
(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style
[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var
left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var
parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt
(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css
(elem,"position")=="fixed";if(elem.getBoundingClientRect){var
box=elem.getBoundingClientRect();add(box.left+Math.max
(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max
(doc.documentElement.scrollTop,doc.body.scrollTop));add(-
doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else
{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add
(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|
d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border
(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")
fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?
offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while
(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/
^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-
parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border
(parent);parent=parent.parentNode;}if((safari2&&(fixed||css
(offsetChild,"position")=="absolute"))||(mozilla&&css
(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-
doc.body.offsetTop);if(fixed)add(Math.max
(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max
(doc.documentElement.scrollTop,doc.body.scrollTop));}results=
{top:top,left:left};}function border(elem){add(jQuery.curCSS
(elem,"borderLeftWidth",true),jQuery.curCSS
(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||
0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend
({position:function(){var left=0,top=0,results;if(this[0]){var
offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/
^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:
0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-
=num(this,'marginLeft');parentOffset.top+=num
(offsetParent,'borderTopWidth');parentOffset.left+=num
(offsetParent,'borderLeftWidth');results={top:offset.top-
parentOffset.top,left:offset.left-parentOffset.left};}return
results;},offsetParent:function(){var offsetParent=this
[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test
(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))
offsetParent=offsetParent.offsetParent;return jQuery
(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var
method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])
return;return val!=undefined?this.each(function(){this==window||
this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?
val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||
this[0]==document?self[i?'pageYOffset':'pageXOffset']||
jQuery.boxModel&&document.documentElement[method]||document.body
[method]:this[0][method];};});jQuery.each(["Height","Width"],function
(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn
["inner"+name]=function(){return this[name.toLowerCase()]()+num
(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]
=function(margin){return this["inner"+name]()+num(this,"border"+tl
+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)
+num(this,"margin"+br):0);};});})();